题目大意
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]);
}
}