[cf1178G]The Awesomest Vertex

2020年论文题,这里给出了一个$o(nlog^{2}n+mlog^{3}n)$的做法,例题3即为原题

1.例题1


题面

给定$n$个一次函数$f_{i}(x)$,$m$次查询$F(x)=max_{i=1}^{n}f_{i}(x)$,强制在线

$1le n,mle 3 imes 10^{5}$,$left|[x^{j}]f_{i}(x) ight|,left|x ight|le 10^{9}$


DS序列

(以下序列被称为Davenport-Schinzel序列,简称DS序列)

定义序列${sigma_{1},sigma_{2},...,sigma_{m}}$是$DS(n,s)$序列,当且仅当满足以下条件:

1.$forall 1le ile m,sigma_{i}$是不超过$n$的正整数

2.${sigma_{i}}$中相邻两数不同(即$forall 1le i<m,sigma_{i} e sigma_{i+1}$)

3.${sigma_{i}}$不存在长度大于$s+1$的子序列,使得其是由两个不同的元素交替构成

引理1:记$lambda_{s}(n)$为最长的$DS(n,s)$​序列长度,则有
$$
lambda_{s}(n)=egin{cases}n&(s=1)\2n-1&(s=2)\2nalpha(n)+O(n)&(s=3)\Theta(n2^{alpha(n)})&(s=4)\Theta(nalpha(n)2^{alpha(n)})&(s=5)\n2^{frac{alpha(n)^{t}}{t!}+O(alpha(n)^{t-1})}&(sge 6)end{cases}
$$
(其中$t=lfloorfrac{s-2}{2} floor$)


题解

称两个函数$f(x)$和$g(x)$是$s$阶交替的,当且仅当$P(x)=[f(x)le g(x)]$是不超过$s+1$段的分段常函数(显然每一段均为0或1,也即大小关系至多变化$s$次)

进一步的,如果可以$o(s)$求出$P(x)$的分段情况,则称两者是可线性合并的

注意到一次函数两两一阶交替,那么记$G(x)=min_{1le ile n,F(x)=f_{i}(x)}i$,显然$G(x)$是一个分段常函数,将其中的常数取出构成一个序列,不难得到这是一个$DS(n,1)$序列

进而分治求出$G(x)$,又因为其可线性合并,预处理复杂度即$o(lambda_{1}(n)log n)=o(nlog n)$

每一次查询二分即可,查询复杂度即$o(mlog n)$

最终,总复杂度为$o(nlog n+mlog n)$,可以通过


拓展

在原问题的基础上,考虑函数在线加入如何处理:

维护一个类似于动态开点线段树的结构,但在区间内线段还不满时不进行合并,显然其均摊复杂度即与分治的复杂度相同,仍为$o(nlog n)$

考虑查询,即需要对$o(log n)$个部分分别二分,这样的复杂度会变为$o(mlog^{2}n)$

考虑分散层叠算法,参考[luogu6466](注意这里要从高到低合并),那么复杂度与修改复杂度相同,均摊后同样是$o(nlog n)$的,同时查询复杂度降为$o(mlog n)$

最终,总复杂度为$o(nlog n+mlog n)$,可以通过


应用

(对于拓展问题)当函数有界限时(即线段),可以用李超线段树$o(nlog^{2}n+mlog n)$实现

而借助此做法,将线段外的部分用$y=-infty$补全,注意到这些函数两两3阶交替且可线性合并,套用上述算法时间复杂度即$o(nalpha(n)log n+mlog n)$,略优于李超线段树

2.例题2


题面

给定$n$个一次函数$f_{i}(x)$,$m$次操作,支持:

1.修改$f_{i}(x)$(仍是一次函数)

2.查询$F(x)=max_{i=1}^{n}f_{i}(x)$,保证$x$单调不下降

强制在线

$1le n,mle 10^{5}$,$left|[x^{j}]f_{i}(x) ight|,left|x ight|le 10^{9}$

(以下为了描述复杂度,均用$m_{i}$代指第$i$种操作的次数)


题解

维护一棵线段树,线段树上每一个节点维护在区间内(对于当前的$x$)取到最大值的函数

当$x$增大时,一个节点被修改的必要条件为满足以下两者之一:1.其子树内有节点被修改;2.其两个儿子维护函数的大小关系发生了变化

由此,维护子树中每一个节点两个儿子下一次大小关系发生变化(相对于当前的$x$)的位置最小值,递归时若更改的$x$没有达到该值即可直接退出

而大小关系发生变化实际上与之前的一阶交替是相似的,因此每一个节点被直接修改(即满足第2个条件)的次数为区间长度,进而会使得其到根路径上$o(log n)$个节点都被访问

考虑每一个节点的贡献,不难发现即为$o(log^{2}n)$,最终总均摊复杂度为$o(nlog^{2}n)$

修改以通常线段树的形式实现即可,实际复杂度为$o(log n)$

而对于均摊的复杂度,可以看作在该叶子上额外增加一个函数,同样贡献为$o(log^{2}n)$

最终,总复杂度为$o(nlog^{2}n+m_{1}log^{2}n+m_{2})$,可以通过

3.例题3


题面

给定一棵$n$个点的树,每一个点有点权$a_{i}$和$b_{i}$

记$R(v)$为$v$所有祖先(包括$v$)构成的集合,则$w_{v}=left|sum_{uin R(v)}a_{u} ight|left|sum_{uin R(v)}b_{u} ight|$

$m$次操作,支持:

1.给定$u$和$x$,将$a_{u}$增加$x$

2.给定$u$,查询$u$子树中(包括$u$)所有节点$w_{v}$的最大值

$1le nle 2 imes 10^{5}$,$1le mle 10^{5}$,$1le ule n$,$0le left|a_{i} ight|,left|b_{i} ight|,xle 5 imes 10^{3}$


题解

注意到$w_{v}=max(sum_{uin R(v)}b_{u}sum_{uin R(v)}a_{u},-sum_{uin R(v)}b_{u}sum_{uin R(v)})$,由于最终也是要取$max$,不妨仅考虑前一项(两项处理方式类似,分别求出后再取$max$即可)

记$k_{v}=sum_{uin R(v)}b_{u}$,$w_{v}$的初值为$k_{v}sum_{uin R(v)}a_{u}$,建立dfs序后即需要支持:

1.给定$l,r$和$x$(其中$xge 0$),令$forall lle ile r,w_{i}=k_{i}x+w_{i}$

2.给定$l$和$r$,查询$max_{lle vle r}w_{v}$

第一个操作似乎比较复杂,但实际上可以看作例题2中$x$区间增加(原来是全局增加),那么即可类似地模仿例题2(当然实现上区别还是较大的),具体做法如下——

每一个位置上的函数为$k_{i}x+w_{i}$(注意这里$w_{i}$是修改后的,可以理解为$x$是变化量),线段树上每一个节点上维护在区间内$x=0$时取到最大值的函数(也即$w_{i}$最大值,若$w_{i}$相同则取$k_{i}$最大的)

再维护子树中每一个节点两个儿子下一次大小关系发生变化的位置最小值,同样如果增加的$x$没有达到该值,直接打懒标记即可(注意修改时也要将该值减小),否则继续递归

这个问题的复杂度并不能简单的描述,需要使用势能分析——

注:这里需要用到一次函数的性质,而不再仅仅是一阶交替和可线性合并

定义一个节点"不平衡"当且仅当其两个儿子中维护函数中的较大者斜率严格小于另一者,再定义其势能为所有不平衡节点的深度平方和

势能的范围为$[0,nlog^{2}n]$,也即初始和结束的势能差至多为$o(nlog^{2}n)$

对于修改,提取出所有被访问过的节点(不包括最后打标记的节点)得到一棵新树

对于这棵新树,去掉必然被访问的节点(即不被修改区间包含的区间),注意到这类点仅有$o(log n)$个,也即最多会使势能增加$o(log^{3}n)$

对于剩下的新树,考虑其中势能的变化:

对于其中的叶子,其必然从不平衡变为平衡(根据结束条件显然)

另一方面,注意到这些区间都被完全覆盖,因此每一个节点上维护函数的斜率和值均单调不下降

由此,考虑从平衡变为不平衡的点,对于其未成为最大值的儿子,其所维护的函数一定发生变化,这个变化即对应于某一个叶子,将其对势能的影响算到该叶子上

同时,由于其未成为最大值,那么每一个叶子至多被"算"一次

由于以$d^{2}$为势能,即使减去$(d-1)^{2}$后其仍会使得势能减去$o(d)$,即与访问的复杂度抵消

综上,(单次)修改均摊复杂度为$o(log^{3}n)$

另外,显然(单次)查询均摊复杂度为$o(log n)$

最终,总复杂度为$o(nlog n+m_{1}log ^{2}n+m_{2}log n)$,可以通过

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define N 200005
  4 #define ll long long
  5 #define L (k<<1)
  6 #define R (L+1)
  7 #define mid (l+r>>1)
  8 vector<int>v[N];
  9 int n,m,p,x,y,a[N],b[N],dfn[N],sz[N],tag[N<<2];
 10 struct Seg{
 11     int tag[N<<2],mn[N<<2];
 12     struct Line{
 13         int k;
 14         ll b;
 15     }f[N<<2];
 16     double get_point(Line x,Line y){
 17         if (x.k==y.k)return 0x3f3f3f3f;
 18         return -1.0*(x.b-y.b)/(x.k-y.k);
 19     }
 20     void upd(int k,int x){
 21         tag[k]+=x,mn[k]-=x;
 22         f[k].b+=(ll)x*f[k].k;
 23     }
 24     void up(int k){
 25         double s=get_point(f[L],f[R]);
 26         if (s<=0)s=0x3f3f3f3f;
 27         s=min(s,(double)0x3f3f3f3f);
 28         mn[k]=min(min(mn[L],mn[R]),(int)floor(s));
 29         if ((f[L].b>f[R].b)||(f[L].b==f[R].b)&&(f[L].k>f[R].k))f[k]=f[L];
 30         else f[k]=f[R];
 31     }
 32     void down(int k){
 33         upd(L,tag[k]);
 34         upd(R,tag[k]);
 35         tag[k]=0;
 36     }
 37     void init(int k,int l,int r,int x,Line y){
 38         if (l==r){
 39             f[k]=y;
 40             return;
 41         }
 42         if (x<=mid)init(L,l,mid,x,y);
 43         else init(R,mid+1,r,x,y);
 44     }
 45     void build(int k,int l,int r){
 46         tag[k]=0;
 47         if (l==r){
 48             mn[k]=0x3f3f3f3f;
 49             return;
 50         }
 51         build(L,l,mid);
 52         build(R,mid+1,r);
 53         up(k);
 54     }
 55     void update(int k,int l,int r,int x,int y,int z){
 56         if ((l>y)||(x>r))return;
 57         if ((x<=l)&&(r<=y)&&(mn[k]>=z)){
 58             upd(k,z);
 59             return;
 60         }
 61         down(k);
 62         update(L,l,mid,x,y,z);
 63         update(R,mid+1,r,x,y,z);
 64         up(k);
 65     }
 66     ll query(int k,int l,int r,int x,int y){
 67         if ((l>y)||(x>r))return -1e18;
 68         if ((x<=l)&&(r<=y))return f[k].b;
 69         down(k);
 70         return max(query(L,l,mid,x,y),query(R,mid+1,r,x,y));
 71     }
 72 }T0,T1;
 73 void dfs(int k,int sa,int sb){
 74     dfn[k]=++dfn[0],sz[k]=1;
 75     sa+=a[k],sb+=b[k];
 76     T0.init(1,1,n,dfn[k],Seg::Line{sb,(ll)sa*sb});
 77     T1.init(1,1,n,dfn[k],Seg::Line{-sb,-(ll)sa*sb});
 78     for(int i=0;i<v[k].size();i++){
 79         dfs(v[k][i],sa,sb);
 80         sz[k]+=sz[v[k][i]];
 81     }
 82 }
 83 int main(){
 84     scanf("%d%d",&n,&m);
 85     for(int i=2;i<=n;i++){
 86         scanf("%d",&x);
 87         v[x].push_back(i);
 88     }
 89     for(int i=1;i<=n;i++)scanf("%d",&a[i]);
 90     for(int i=1;i<=n;i++)scanf("%d",&b[i]);
 91     dfs(1,0,0);
 92     T0.build(1,1,n),T1.build(1,1,n);
 93     for(int i=1;i<=m;i++){
 94         scanf("%d%d",&p,&x);
 95         if (p==1){
 96             scanf("%d",&y);
 97             T0.update(1,1,n,dfn[x],dfn[x]+sz[x]-1,y);
 98             T1.update(1,1,n,dfn[x],dfn[x]+sz[x]-1,y);
 99         }
100         if (p==2)printf("%lld
",max(T0.query(1,1,n,dfn[x],dfn[x]+sz[x]-1),T1.query(1,1,n,dfn[x],dfn[x]+sz[x]-1)));
101     }
102     return 0;
103 }
View Code
原文地址:https://www.cnblogs.com/PYWBKTDA/p/15391684.html