树剖板子题单 log 做法

· · 题解

我觉得不会有人无聊到写这个长达 6KB 还比树剖慢的东西,除了我。

这篇题解不是树剖题解。

想要给这题一个 1log 的做法,但是不会 Top Tree,所以对子树处理取了个巧。

考虑贡献可拆成链对链,链对子树,子树对链,子树对子树。链又可以被容斥成两点到根,lca 及其父亲到根四条根链,故而下文只考虑根链(有四倍常数)。

$acc_u$:u 的祖先。 $tag_u$:u 处修改贡献之和,不同贡献对的 $tag$ 相互独立。 ## 子树对子树 最简单的部分。处理出 dfn 序和子树大小然后上树状数组区间加区间求和即可。 具体地,一个点的子树在 dfn 序上的范围是 $[dfn_x,dfn_x+sz_x-1]$,我们把两种操作都对应成了区间操作,这里和树剖是类似的。 ![](https://cdn.luogu.com.cn/upload/image_hosting/gm28pk68.png) ## 根链对子树 如上图左,则点 $u$ 的答案为: $$\sum_{x \in sub_u} (dep_x-dep_u+1)tag_x$$ $$=({\sum_{x \in sub_u} (dep_x+1)tag_x})-dep_u({\sum_{x \in sub_u} tag_x})$$ 上 dfn 树状数组单点加子树查维护括号里两个东西即可。 ## 子树对根链 如上图右,与第二种情况相反。 $$\sum_{x \in acc_u} (dep_u-dep_x+1)tag_x$$ $$=dep_u({\sum_{x \in acc_u} tag_x})-({\sum_{x \in acc_u} (dep_x-1)tag_x})$$ 这里是子树加单点查。 ## 链对链 [全局平衡二叉树](https://oi-wiki.org/ds/global-bst/)的优势区间,直接上就是。 ## 全局平衡二叉树 大致讲一下吧。我们想想链剖为什么复杂度会多一只 log,它跳重链要跳 log 次,每次查询要 log。前者不好省略,我们考虑让后者去迎合前者。 我们先做一个小优化:原本线段树是大家用一棵,我们给他改成一条每条重链开一棵。但是这并没有解决时间复杂度的问题,~~而且成功在子树问题方面获得了劣势~~。 为啥还是不好呢?我们看图: ![](https://cdn.luogu.com.cn/upload/image_hosting/azbmw3n8.png) 这个 6 号点到根的路径过了七层线段树节点,可是 5 号点只过了三层,这就不平衡了,不利于时间复杂度。 接下来,我们要让这些线段树变成全局平衡的形式。全局平衡,说白了就是你这棵线段树不要只顾着自己平衡,而是要往远了看,底下可能深的就浅一点,底下可能浅的就深一点。 具体地,我们每次以**轻子树加自己的大小总和**为权($=sz_u-sz_{wson_u}$),求出线段树区间内的加权中点(lower_bound$(\frac{sum+1}{2})$)作为这个线段树节点的 mid。(细节:由于线段树和平衡树不一样,当 mid 取到右端点的时候要让他减一。) 效果好不好呢?我们还是看图。 ![](https://cdn.luogu.com.cn/upload/image_hosting/7blx2xbk.png) 可以看到,全局平衡后在底下深的就浅了。6 号点仅需要爬六层,5 号点却要爬四层了。 每次的操作是重链的一段前缀。以 9 号点为例,其需要计入答案的节点用绿色圈出,会经过但不算答案的点用蓝色圈出。(去原树看看也确实是 1,2,3,9 在其到根路径上。) 大致分析一下复杂度,在你跳到根的过程中,首先轻边只会经过 log 条,这是重剖所保证的性质。那重边(线段树的边)呢?往上跳一步,他的轻重子树大小之和是要翻倍的(因为取的是带权中点,最大的一边尽可能小)。虽然有可能出现某个点轻子树非常非常大导致翻倍失败的情况,但这不影响复杂度,仅仅是一个常数(我不会分析)。那么重边也只会跳 $O(\log)$ 条(注意和轻边的严格 log 相区分)。那么时间复杂度就是 $O(\log)$ 的。 ~~我的全局平衡二叉树喜欢用线段树形式,但貌似主流都是平衡树形式的样子。~~ 于是我们获得了 $O(n\log n)$ 时间复杂度的做法。但是全局平衡二叉树常数太大了。[最慢 381ms](https://www.luogu.com.cn/record/200748741) 甚至打不过我的线段树+树剖更不用说树状数组了。 什么?你问我为什么不能写进一个全局平衡二叉树里?我没见过代码,自己不会分讨子树乱七八糟的永久化标记,所以就没这么干。oi-wiki 上说可以,有没有神犇来试一试? 为啥全局平衡二叉树没有【模板】题? ## code 很多封装。不封装变量和函数的命名会让人疯掉。 ```cpp #include<bits/stdc++.h> using namespace std; const int maxn=1e5+10; typedef long long ll; struct edge{ int v,nxt; }ed[maxn<<1]; int head[maxn],edcnt; void adde(int u,int v){ ed[++edcnt].nxt=head[u]; ed[edcnt].v=v; head[u]=edcnt; } ll mod; ll add(ll x,ll y){ ll res=x+y; if(res>=mod) res-=mod; if(res<=-mod) res+=mod; if(res<=mod||res>=mod) return res%mod; return res; } ll mul(ll x,ll y){return (x*y)%mod;} template<typename T> void addto(T &x,T y){x=add(x,y);} int root; int dep[maxn],sz[maxn],fa[maxn]; int dfn[maxn],redfn[maxn],dfncnt; struct GBST{ struct node{ int l,r,mid; int ls,rs,fa,rt; int sz; ll add_lazy,sumv; void clear(){memset(this,0,sizeof(node));} }tr[maxn<<1]; int idx; int newnode(int l,int r,int mid){ int p=++idx;tr[p].clear(); tr[p].l=l;tr[p].r=r;tr[p].mid=mid; return p; } int wson[maxn],top[maxn]; void dfs1(int u,int f,int d){ sz[u]=1;fa[u]=f;dep[u]=d;wson[u]=0; ++dfncnt;dfn[dfncnt]=u;redfn[u]=dfncnt; for(int i=head[u];i;i=ed[i].nxt){ int v=ed[i].v;if(v==f) continue; dfs1(v,u,d+1);sz[u]+=sz[v]; if(!wson[u]||sz[wson[u]]<sz[v]) wson[u]=v; } } void pushup(int p){ tr[p].sumv=add(mul(tr[p].sz,tr[p].add_lazy), add(tr[tr[p].ls].sumv,tr[tr[p].rs].sumv)); } int b[maxn],pbs[maxn],bcnt; int build(int l,int r,int rt){ if(r<l) return 0; if(l==r){ int mid=l,p=b[mid];if(!rt) rt=p; tr[p].clear();tr[p].l=l;tr[p].r=r;tr[p].mid=mid; tr[p].rt=rt;tr[p].sz=1;tr[p].sumv=0; return p; }int mid=lower_bound(pbs+l,pbs+r+1,(pbs[l-1]+pbs[r]+1)/2)-pbs; if(mid==r) mid--;int p=newnode(l,r,mid);if(!rt) rt=p; int ls=build(l,mid,rt),rs=build(mid+1,r,rt); tr[p].ls=ls,tr[p].rs=rs;tr[p].rt=rt; tr[p].sz=tr[tr[p].ls].sz+tr[tr[p].rs].sz; if(ls) tr[ls].fa=p;if(rs) tr[rs].fa=p; pushup(p);return p; } int dfs2(int u){ for(int p=u;p;p=wson[p]) for(int i=head[p];i;i=ed[i].nxt){ int v=ed[i].v;if(v==fa[p]||v==wson[p]) continue; tr[dfs2(v)].fa=p;///not u!!! }bcnt=0; for(int p=u;p;p=wson[p]){ b[++bcnt]=p;top[p]=u; pbs[bcnt]=pbs[bcnt-1]+sz[p]-sz[wson[p]];//no +1 }return build(1,bcnt,0); } void addf(int p,ll v){ addto(tr[p].add_lazy,v); addto(tr[p].sumv,mul(tr[p].sz,v)); } void chg_tree(int p,int l,int r,ll v){ if(!p||r<l) return ; if(l<=tr[p].l&&tr[p].r<=r) return addf(p,v); if(l<=tr[p].mid) chg_tree(tr[p].ls,l,r,v); if(tr[p].mid<r) chg_tree(tr[p].rs,l,r,v);pushup(p); } ll get_lns(int p,int l,int r){ int L=max(l,tr[p].l),R=min(r,tr[p].r); return max(0,R-L+1); } ll query_tree(int p,int l,int r){ if(!p||r<l) return 0; if(l<=tr[p].l&&tr[p].r<=r) return tr[p].sumv; ll res=mul(tr[p].add_lazy,get_lns(p,l,r)); if(r<=tr[p].mid) return add(query_tree(tr[p].ls,l,r),res); if(tr[p].mid<l) return add(query_tree(tr[p].rs,l,r),res); return add(res,add(query_tree(tr[p].ls,l,r),query_tree(tr[p].rs,l,r))); } int get_lca(int x,int y){ while(top[x]!=top[y]){ if(dep[top[x]]<dep[top[y]]) swap(x,y); x=fa[top[x]]; }return dep[x]<dep[y]?x:y; } struct line{int p,l,r;};vector<line> lns; void split(int x,int y){ lns.clear(); while(top[x]!=top[y]){ if(dep[top[x]]<dep[top[y]]) swap(x,y); lns.push_back({tr[x].rt,1,tr[x].mid}); x=tr[tr[x].rt].fa; }if(dep[x]>dep[y]) swap(x,y); lns.push_back({tr[x].rt,tr[x].mid,tr[y].mid}); } void chg(ll v){for(line x:lns) chg_tree(x.p,x.l,x.r,v);} ll query(){ ll res=0; for(line x:lns) addto(res,query_tree(x.p,x.l,x.r)); return res; } void prepare(){ dfncnt=0;dfs1(root,0,1);idx=sz[root];dfs2(root); } };GBST tr_LL; struct BIT{ int n;ll a[maxn],ai[maxn]; void prepare(int n_){n=n_;for(int i=1;i<=n;i++) ai[i]=a[i]=0;} void adda(int p,ll v){for(;p<=n;p+=p&-p) addto(a[p],v);} void addai(int p,ll v){for(;p<=n;p+=p&-p) addto(ai[p],v);} void chg(int l,int r,ll v){ adda(l,v);adda(r+1,-v); addai(l,mul(l-1,v));addai(r+1,-mul(r,v)); } ll querya(int p){ll s=0;for(;p;p-=p&-p) addto(s,a[p]);return s;} ll queryai(int p){ll s=0;for(;p;p-=p&-p) addto(s,ai[p]);return s;} ll query(int p){return add(mul(p,querya(p)),-queryai(p));} ll query(int l,int r){return add(query(r),-query(l-1));} };BIT tr_SS; struct BIT2{ int n;ll a[maxn]; void prepare(int n_){n=n_;for(int i=1;i<=n;i++) a[i]=0;} void chg(int p,ll v){for(;p<=n;p+=p&-p) addto(a[p],v);} ll query(int p){ll s=0;for(;p;p-=p&-p) addto(s,a[p]);return s;} ll query(int l,int r){return add(query(r),-query(l-1));} };BIT2 tr_LS,tr_LSi; struct DFOT{ BIT2 tr; void prepare(int n_){tr.prepare(n_);} void chg(int p,ll v){ tr.chg(redfn[p],v);tr.chg(redfn[p]+sz[p],-v); } ll query(int p){return tr.query(redfn[p]);} };DFOT tr_SL,tr_SLi; void path_add(int x,int y,ll v){ tr_LL.split(x,y);tr_LL.chg(v); int lca=tr_LL.get_lca(x,y); auto up_add=[](int p,ll val){ if(!p) return ; tr_LS.chg(redfn[p],val);tr_LSi.chg(redfn[p],mul(dep[p]+1,val)); };up_add(x,v);up_add(y,v);up_add(lca,-v);up_add(fa[lca],-v); } void sub_add(int x,ll v){ tr_SS.chg(redfn[x],redfn[x]+sz[x]-1,v); tr_SL.chg(x,v);tr_SLi.chg(x,mul(dep[x]-1,v)); } ll path_query(int x,int y){ tr_LL.split(x,y);ll res=tr_LL.query(); int lca=tr_LL.get_lca(x,y); auto up_query=[&](int p,ll v){ if(!p) return ; addto(res,v*add(mul(dep[p],tr_SL.query(p)),-tr_SLi.query(p))); };up_query(x,1);up_query(y,1);up_query(lca,-1);up_query(fa[lca],-1); return (res%mod+mod)%mod; } ll sub_query(int x){ ll res=tr_SS.query(redfn[x],redfn[x]+sz[x]-1); addto(res,tr_LSi.query(redfn[x],redfn[x]+sz[x]-1)); addto(res,-mul(tr_LS.query(redfn[x],redfn[x]+sz[x]-1),dep[x])); return (res%mod+mod)%mod; } int n,m; ll a[maxn]; int main(){ scanf("%d%d%d%lld",&n,&m,&root,&mod); for(int i=1;i<=n;i++) scanf("%lld",&a[i]); for(int i=1,u,v;i<=n-1;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u); tr_LL.prepare(),tr_SL.prepare(n),tr_SLi.prepare(n); tr_LS.prepare(n),tr_LSi.prepare(n),tr_SS.prepare(n); for(int i=1;i<=n;i++) path_add(i,i,a[i]); for(int i=1;i<=m;i++){ int op;scanf("%d",&op); if(op==1){ int x,y;ll v;scanf("%d%d%lld",&x,&y,&v); path_add(x,y,v); }else if(op==2){ int x,y;scanf("%d%d",&x,&y); printf("%lld\n",path_query(x,y)); }else if(op==3){ int x;ll v;scanf("%d%lld",&x,&v); sub_add(x,v); }else if(op==4){ int x;scanf("%d",&x); printf("%lld\n",sub_query(x)); } } return 0; } ```