您的位置:首页 > 其它

CF 372D Choosing Subtree is Fun

2013-12-14 02:14 447 查看
题目链接: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
;
int n,K;
int f
[20],dep
,id
,cnt
,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);
}


  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: