摧毁图状树

来源:互联网 发布:2016淘宝刷单安全吗 编辑:程序博客网 时间:2024/04/28 23:10

题目大意

Q次询问,每次给一个k。
将树用尽量少的长度不超过k的祖先后代链覆盖,使得每个点至少覆盖一次。

贪心

对偶成选择尽量多的点,使得任意长度不超过k的祖先后代链上至多一个点被选择。
这样转化则贪心很显然。尽量选深度大的点。
如果有t个叶子,选取的点至多为t+(n-t)/k。
因为叶子一定会被选择,同样删去所有被选择的点后,每个联通块大小不会小于k(否则一定在原来的树中存在长度不超过k的祖先后代链上有超过1个点被选择)。
因此可以线性实现对一个k求答案。
观察答案的表达式可以发现它只有根号种取值(可能有点不严谨)。
因此可以分块。
直接二分找下一个是很慢的,可以用CDQ分治优化。

#include<cstdio>#include<algorithm>#define fo(i,a,b) for(i=a;i<=b;i++)#define fd(i,a,b) for(i=a;i>=b;i--)#define max(a,b) (a>b?a:b)#define min(a,b) (a<b?a:b)using namespace std;const int maxn=100000+10;int sg[maxn],nfd[maxn],fa[maxn],h[maxn],go[maxn*2],next[maxn*2];int a[maxn],ans[maxn];int i,j,k,l,r,mid,t,n,m,tot,top,cnt,num,mx;bool czy,gjx;int read(){    int x=0,f=1;    char ch=getchar();    while (ch<'0'||ch>'9'){        if (ch=='-') f=-1;        ch=getchar();    }    while (ch>='0'&&ch<='9'){        x=x*10+ch-'0';        ch=getchar();    }    return x*f;}void add(int x,int y){    go[++tot]=y;    next[tot]=h[x];    h[x]=tot;}void dg(int x,int y,int z){    nfd[++top]=x;    fa[x]=y;    mx=max(mx,z);    int t=h[x];    while (t){        if (go[t]!=y){            dg(go[t],x,z+1);        }        t=next[t];    }}int query(int x){    k=x;    l=0;    int i;    fo(i,1,n) sg[i]=0;    fd(i,n,1){        if (sg[nfd[i]]==0) sg[nfd[i]]=k,l++;        sg[fa[nfd[i]]]=max(sg[fa[nfd[i]]],sg[nfd[i]]-1);    }    return l;}void solve(int l,int r,int lv,int rv){    if (lv==rv){        int i;        fo(i,l,r) ans[i]=lv;        return;    }    int mid=(l+r)/2;    int mlv=query(mid),mrv=query(mid+1);    solve(l,mid,lv,mlv);    solve(mid+1,r,mrv,rv);}int main(){    freopen("tree.in","r",stdin);freopen("tree.out","w",stdout);    n=read();    fo(i,1,n-1){        j=read();k=read();        add(j,k);add(k,j);    }    dg(1,0,1);    solve(1,mx,query(1),query(mx));    m=read();    fo(i,1,m){        k=read();        k=min(k,mx);        printf("%d\n",ans[k]);    }}