Lost My Music:倍增实现可持久化单调栈维护凸包
题目就是求树上每个节点的所有祖先中(ci-cj)/(dj-di)的最小值。
那么就是(ci-cj)/(di-dj)的最大值了。
对于每一个点,它的(ci,di)都是二维坐标系里的一个点
要求的就是祖先节点的所有点与目前节点连线的最小斜率
比较容易想到单调栈优化,像斜率优化dp一样
但是关键是本题在树上,会有很多麻烦的操作。
当搜到某一个儿子时可能会弹很多栈,而回溯的过程中需要把它们加回来。
如果暴力执行的话,会在蒲公英图退化为n2。
考虑优化:现在的关键就是在于在一个元素可能被弹栈/还原多次的情况下如何快速维护?
考虑倍增。st[i][j]表示从j的代表元素在栈里向前跳2i个元素后的代表元素是谁。我们可以对于每一个节点都开一个log级别的栈。
那么只需要探讨要把某一个元素接在父亲栈里的哪一个位置就好了。单调栈当然满足单调性,故可以倍增跳跃。
那么对于父亲节点的栈其实我们并不改变它。
一个元素因为可能弹栈所以后端的元素可能不唯一,但是入栈时间确定那么前面的元素就唯一确定。
单调栈顶就是最优决策点。
研究代码,很好理解。
1 #include<cstdio> 2 int st[22][500005],fir[500005],l[500005],to[500005],cnt,n,c[500005]; 3 int q[500005],fa[500005],dep[500005];long double ans[500005]; 4 void link(int a,int b){l[++cnt]=fir[a];fir[a]=cnt;to[cnt]=b;} 5 long double calc(int x,int y){return 1.0L*(c[x]-c[y])/(dep[y]-dep[x]);} 6 void pop_push(int p){ 7 int x=fa[p]; 8 for(int i=20;i>=0;--i)if(st[0][st[i][x]]&&calc(st[0][st[i][x]],st[i][x])<calc(st[i][x],p))x=st[0][st[i][x]]; 9 if(st[0][x]&&calc(st[0][x],x)<calc(x,p))x=st[0][x]; 10 st[0][p]=x;ans[p]=calc(st[0][p],p); 11 for(int i=1;i<=20;++i)st[i][p]=st[i-1][st[i-1][p]]; 12 } 13 int main(){ 14 scanf("%d",&n);q[1]=1; 15 for(int i=1;i<=n;++i)scanf("%d",&c[i]); 16 for(int i=2;i<=n;++i)scanf("%d",&fa[i]),link(fa[i],i); 17 for(int h=1,t=1;h<=t;++h){ 18 dep[q[h]]=dep[fa[q[h]]]+1;pop_push(q[h]); 19 for(int i=fir[q[h]];i;i=l[i])q[++t]=to[i]; 20 } 21 for(int i=2;i<=n;++i)printf("%.8Lf\n",ans[i]); 22 }
$Fate \ is \ Fake$