題目連結:http://codeforces.com/problemset/problem/372/D
題意:給出一棵樹。找到一個樹的子集。在該子集包含節點數不超過K的情況下,使得該子集包含的連續編號的節點數最大?
思路: 首先,DFS一次,得到一個DFS序,就是每個節點是第幾個被DFS到的,這個程式中用cnt[u]表示,以及記錄在DFS序中某幾次遍歷的編號是誰,這個用id[i]表示。id[cnt[u]]=u。然後二分答案。對於二分的值M,保證每次插入到set中的節點數都是連續的M個,比如第一次一次性插入M-1個點,然後每次插入一個,然後到下一個時刪除已經插入到set中的最前面的那個。插入刪除時更新子集中的點的個數。小於等於K則該二分值可達到。下面說在插入刪除時如何更新答案。插入節點時插入的是每個節點的cnt值,即DFS序中的編號。每次插入時找到該編號的前一個編號的點以及後一個編號的點,不妨設為pre和next。此次插入的點需要新增的點數為dep[u]-dep[LCA(pre,u)]-dep[LCA(next,u)]+dep[LCA(pre,next)]。
vector<int> g[N];
int n,K;
int f[N][20],dep[N],id[N],cnt[N],tot;
set<int> S;
void DFS(int u,int pre)
{
f[u][0]=pre;
id[cnt[u]=++tot]=u;
int i,v;
FOR0(i,SZ(g[u]))
{
v=g[u][i];
if(v!=pre) dep[v]=dep[u]+1,DFS(v,u);
}
}
int LCA(int u,int v)
{
if(dep[u]>dep[v]) swap(u,v);
int k=dep[v]-dep[u],i;
FOR0(i,20) if(k&(1<<i)) v=f[v][i];
if(v==u) return u;
for(i=19;i>=0;i--) if(f[u][i]&&f[v][i]&&f[u][i]!=f[v][i])
{
u=f[u][i];
v=f[v][i];
}
return f[u][0];
}
int sum;
void modify(int op,int u)
{
set<int>::iterator it=S.lower_bound(cnt[u]);
if(*it==cnt[u])
{
S.erase(cnt[u]);
it=S.lower_bound(cnt[u]);
}
if(!S.size())
{
if(op==1) S.insert(cnt[u]);
return;
}
int pre,next;
if(it==S.begin()) pre=id[*(--S.end())];
else pre=id[*(--it)],it++;
if(it==S.end()) next=id[*S.begin()];
else next=id[*it];
int temp=dep[u]-dep[LCA(pre,u)]-dep[LCA(next,u)]+dep[LCA(pre,next)];
if(op==1) sum+=temp,S.insert(cnt[u]);
else sum-=temp;
}
int OK(int M)
{
sum=0;
S.clear();
int i;
FOR1(i,M-1) modify(1,i);
for(i=M;i<=n;i++)
{
modify(1,i);
if(sum+1<=K) return 1;
modify(0,i+1-M);
}
return 0;
}
int main()
{
RD(n,K);
int i,j;
FOR1(i,n-1)
{
int u,v;
RD(u,v);
g[u].pb(v);
g[v].pb(u);
}
DFS(1,0);
for(i=1;i<20;i++) FOR1(j,n) f[j][i]=f[f[j][i-1]][i-1];
int low=1,high=n,M,ans;
while(low<=high)
{
M=(low+high)>>1;
if(OK(M)) ans=M,low=M+1;
else high=M-1;
}
PR(ans);
}