CF 372D Choosing Subtree is Fun

weixin_34162629發表於2019-01-08

題目連結: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);
}

  

相關文章