题目大意:给定一棵N个节点的有根树,根节点为1。Q次询问,每次给定一个K,用最少的操作次数遍历完整棵树,输出最少操作次数。每次操作可以选择访问不超过K个未访问的点,且这些点的父亲必须在之前被访问过。
设s[i]表示深度大于i的点的个数
首先,我们需要知道,最优方案一定是先用i步拿完了前i层的点,然后再每次取k个,直到取完
证明如下:
首先我们先一层一层的拿,当这层的点大于k个时,我们挑选其中任意k个,当这层点的个数少于k个时,我们就可以拿前几层没拿的点,这样直到某一层的时候,满足了前面的点都被拿光,而之后有方案可以保证每次都能拿满k个,这一层就是最后的i
所以对于每一个k,相当于求
max(i+⌈s[i]k⌉),1≤i≤maxdep
化简之后就变成了
⌈max(ki+s[i])k⌉,1≤i≤maxdep
也就是说对于每一个k,只需要求出
max(ki+s[i])
就好了
我们可以把ix+s[i]当做一个一次函数,然后对于不同的i形成的这些一次函数维护一个凸壳,然后把k排好序在上面扫一遍就可以求出最大值了
#include<iostream>
#include<cstdio>
#define N 1000010
using namespace std;
int a[N];
int to[N],nxt[N],pre[N],cnt;
void ae(int ff,int tt)
{
cnt++;
to[cnt]=tt;
nxt[cnt]=pre[ff];
pre[ff]=cnt;
}
int d[N],fa[N],M;
int c[N];
void build(int x)
{
int i,j;
d[x]=d[fa[x]]+1;
M=max(M,d[x]);
c[d[x]]++;
for(i=pre[x];i;i=nxt[i])
{
j=to[i];
build(j);
}
}
int ans[N];
int s[N],t;
int main()
{
int n,m;
scanf("%d%d",&n,&m);
int i,j,x,y;
for(i=1;i<=m;i++)
scanf("%d",&a[i]);
for(i=2;i<=n;i++)
{
scanf("%d",&fa[i]);
ae(fa[i],i);
}
build(1);
for(i=M-1;i>=1;i--)
c[i]+=c[i+1];
s[1]=1;s[2]=2;t=2;
for(i=3;i<=M;i++)
{
while(t>1&&(s[t]-s[t-1])*(c[i]-c[s[t-1]])-(c[s[t]]-c[s[t-1]])*(i-s[t-1])>=0) t--;
t++;s[t]=i;
}
int tmp=0;
cout<<endl;
for(i=1;i<=t;i++)
cout<<s[i]<<' '<<c[s[i]]<<endl;*/
j=1;
for(i=1;i<=n;i++)
{
while(j<t&&i*s[j]+c[s[j]]<i*s[j+1]+c[s[j+1]]) j++;
ans[i]=s[j];
}
for(i=1;i<=m;i++)
{
x=a[i];
if(x>=n) printf("%d",M);
else printf("%d",max(ans[x]-1+(c[ans[x]]+x-1)/x,M));
if(i!=m) putchar(' ');
}
}