摧毁图状树

49 篇文章 1 订阅
18 篇文章 1 订阅

题目大意

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]);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值