Luogu P3833 [SHOI2012]魔法树
此题理论最优解
题目大意:路径修改,子树求和
明显是树剖的模板,但树剖的时间复杂度高达了优秀的$\Theta(Q \;log^2n)$,而实际上树上差分可以把时间复杂度降到$\Theta(Q \;logn)$。
设$tag[x]$为$1$到$x$的路径上全都加了这个值,显然对于$(x,y)$这条路径加$d$的操作可以看作
$$tag[x]+=d,tag[y]+=d,tag[LCA(x,y)]-=d,tag[prt[LCA(x,y)]]-=d$$
若查询以$root$为根的子树和,考虑子树内每个点$x$对答案的贡献是
$$(dep[x]-dep[root]+1)\times tag[x]$$
上式的意义是在$(x,root)$这条路径每个点都被加了$tag[x]$,对上式求和得到
$$\sum \;((dep[x]-dep[root]+1)\times tag[x])$$
$$\sum \;(dep[x]\times tag[x]-(dep[root]-1)\times tag[x])$$
$$\sum \;(dep[x]\times tag[x])-(dep[root]-1)\times\sum\;(tag[x])$$
两个树状数组维护$dep[x]\times tag[x]$和$tag[x]$就好了,因为在$DFS$序上子树是连续的区间,直接查询即可。
因为我的丑代码常数太大,把理论最优解跑还得不如树剖
#include<iostream> #include<iomanip> #include<cstring> #include<cstdio> #include<cmath> #include<algorithm> using namespace std; #define int long long inline int read() { char ch; bool bj=0; while(!isdigit(ch=getchar())) bj|=(ch=='-'); int res=ch^(3<<4); while(isdigit(ch=getchar())) res=(res<<1)+(res<<3)+(ch^(3<<4)); return bj?-res:res; } void printnum(int x) { if(x>9)printnum(x/10); putchar(x%10+'0'); } inline void print(int x,char ch) { if(x<0) { putchar('-'); x=-x; } printnum(x); putchar(ch); } const int MAXN=1e5+5; int n,m,h[MAXN],cnt; int top[MAXN],size[MAXN],prt[MAXN],son[MAXN],dep[MAXN],tot,tid[MAXN]; struct Edge { int to,nxt; } w[MAXN<<1]; int c1[MAXN],c2[MAXN]; inline void AddEdge(int x,int y) { w[++cnt].nxt=h[x]; w[cnt].to=y; h[x]=cnt; } void DFS1(int x,int fa,int depth) { dep[x]=depth; prt[x]=fa; size[x]=1; for(int i=h[x]; i; i=w[i].nxt) { int v=w[i].to; if(v==fa)continue; DFS1(v,x,depth+1); if(size[v]>size[son[x]])son[x]=v; size[x]+=size[v]; } } void DFS2(int x,int sp) { top[x]=sp; tid[x]=++tot; if(!son[x])return; DFS2(son[x],sp); for(int i=h[x]; i; i=w[i].nxt) { int v=w[i].to; if(v==prt[x]||v==son[x])continue; DFS2(v,v); } } inline int LCA(int x,int y) { while(top[x]^top[y]) { if(dep[top[x]]<dep[top[y]])swap(x,y); x=prt[top[x]]; } if(dep[x]>dep[y])swap(x,y); return x; } inline int lowbit(int x) { return x&(-x); } inline void update(int x,int d,int c[]) { if(!x)return; while(x<=n)c[x]+=d,x+=lowbit(x); } inline int query(int x,int c[]) { int sum=0; while(x)sum+=c[x],x-=lowbit(x); return sum; } inline void add(int x,int d) { update(tid[x],d,c1); update(tid[x],dep[x]*d,c2); } inline int ask(int x,int y,int c[]) { return query(y,c)-query(x-1,c); } signed main() { n=read(); int x,y,d; for(int i=1; i<n; i++) { x=read()+1; y=read()+1; AddEdge(x,y); AddEdge(y,x); } DFS1(1,0,1); DFS2(1,1); m=read(); char ch; while(m--) { do ch=getchar(); while(ch!='A'&&ch!='Q'); if(ch=='A') { x=read()+1; y=read()+1; d=read(); int u=LCA(x,y); add(x,d); add(y,d); add(u,-d); add(prt[u],-d); } else { x=read()+1; int tmp=ask(tid[x],tid[x]+size[x]-1,c1); print(ask(tid[x],tid[x]+size[x]-1,c2)-tmp*(dep[x]-1),'\n'); } } return 0; }
为什么还是写了两个DFS?因为我要树剖求LCA