题目大意:
给定一个$n(nle10^6)$个结点的有根树,从根结点开始染色。每次可以染和已染色结点相邻的任意$k$个结点。$q(qle10^6)$组询问,每次给定$k$,问至少需要染几次?
思路:
显然$ans=maxleft{i+leftlceilfrac{sum[i]}k
ight
ceil
ight}$。而这样做是$O(nq)$的,观察到原式$=leftlceilfrac{maxleft{ki+sum[i]
ight}}k
ight
ceil$。$max$中是关于$k$的一次函数,考虑使用斜率优化,做到$O(n+q)$。
1 #include<cmath> 2 #include<cstdio> 3 #include<cctype> 4 #include<algorithm> 5 inline int getint() { 6 register char ch; 7 while(!isdigit(ch=getchar())); 8 register int x=ch^'0'; 9 while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0'); 10 return x; 11 } 12 const int N=1e6+1; 13 int k[N],sum[N],h[N],head=1,tail,sz,max,ans[N],q[N]; 14 struct Edge { 15 int to,next; 16 }; 17 Edge e[N<<1]; 18 inline void add_edge(const int &u,const int &v) { 19 e[++sz]=(Edge){v,h[u]};h[u]=sz; 20 } 21 void dfs(const int &x,const int &dep) { 22 sum[dep]++; 23 max=std::max(max,dep); 24 for(int i=h[x];i;i=e[i].next) { 25 const int &y=e[i].to; 26 dfs(y,dep+1); 27 } 28 } 29 inline double calc (const int &i,const int &j) { 30 return (double)(sum[j]-sum[i])/(i-j); 31 } 32 int main() { 33 const int n=getint(); 34 for(register int i=0;i<=k[0];i++) k[i]=getint(); 35 for(register int i=2;i<=n;i++) { 36 add_edge(getint(),i); 37 } 38 dfs(1,0); 39 for(register int i=max;~i;i--) sum[i]+=sum[i+1]; 40 for(register int i=max;~i;i--) { 41 while(head<tail&&calc(q[tail-1],q[tail])<=calc(q[tail],i)) tail--; 42 q[++tail]=i; 43 } 44 for(register int i=n;i;i--) { 45 while(head<tail&&calc(q[head],q[head+1])>=i) head++; 46 ans[i]=q[head]+ceil((double)sum[q[head]]/i); 47 } 48 for(register int i=1;i<=k[0];i++) { 49 printf("%d%c",k[i]<=n?ans[k[i]]:max+1," "[i==k[0]]); 50 } 51 return 0; 52 }