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 }
View Code
posted @ 2019-08-18 07:07  DeepinC  阅读(320)  评论(0编辑  收藏  举报