BZOJ3091城市旅行——LCT區間信息合並
題目描述
輸入
輸出
樣例輸入
4 51 3 2 5
1 2
1 3
2 4
4 2 4
1 2 4
2 3 4
3 1 4 1
4 1 4
樣例輸出
16/36/1
提示
對於所有數據滿足 1<=N<=50,000 1<=M<=50,000 1<=Ai<=10^6 1<=D<=100 1<=U,V<=N
前三個操作都很簡單了,LCT就能維護,重點是第四個操作。
求一個區間所有子區間的區間和之和,直接求所有區間和不好求,我們換一種角度去做。
考慮每個點對區間的貢獻,假設當前區間是[l,r],對於區間中的點k(l<=k<=r),它的貢獻就是它的點權*(k-l+1)*(r-k+1)。
那麽我們維護區間答案,考慮怎麽上傳及修改?
先說上傳,就是將一個點的左兒子區間+這個點+這個點的右兒子區間合並。
我們設size[x]為x子樹大小,也就是x子樹所代表的區間的長度;ls代表左子樹,rs代表右子樹。
對於左區間,每個點的貢獻要加上它從左往右數的排名*它的點權*(1+size[rs])。
對於右區間,每個點的貢獻要加上它從右往左數的排名*它的點權*(1+size[ls])。
對於點x要加上它的點權*(1+size[ls])*(1+size[rs])。
發現排名*點權的和無法直接求,因此還要維護兩個信息lv[x],rv[x],分別代表x子樹所代表區間中每個點點權*從左/從右排名的和。
再看看這兩個信息怎麽合並,就以lv[x]為例吧,先將左右節點的lv加上,左子樹lv不變,右子樹的lv發現每個點排名都加了(1+size[ls]),只要再加上右子樹權值和*(1+size[ls])就好了。
綜上所述,我們需要維護六個變量val,sum,lv,rv,size,ans,分別代表單點權值、子樹權值和、點權*從左數排名之和、點權*從右數排名之和、子樹節點數、區間所有子區間和之和即答案。
再說怎麽修改?設n代表區間長,v為修改時的增量
val和sum比較常規在這就不說了。
lv和rv都加了
而ans則加了
最後這個推一個通項公式就好了。
知道怎麽上傳和修改後剩下的就LCT基本操作了。
但要註意翻轉時也要把lv和rv交換且旋到原樹根的操作splay之後不能只打標記,要先把當前點左右子樹翻轉,否則當前點的lv和rv是反的。
#include<set> #include<map> #include<queue> #include<stack> #include<cmath> #include<bitset> #include<vector> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define ll long long #define ls s[rt][0] #define rs s[rt][1] using namespace std; int n,m; int x,y; int opt; ll z; int r[50010]; int s[50010][2]; int f[50010]; int st[50010]; ll lv[50010]; ll rv[50010]; ll sum[50010]; ll size[50010]; ll val[50010]; ll ans[50010]; ll a[50010]; ll p,q; ll res; int get(int rt) { return s[f[rt]][1]==rt; } int is_root(int rt) { return s[f[rt]][1]!=rt&&s[f[rt]][0]!=rt; } void add(int rt,ll v) { val[rt]+=v; sum[rt]+=v*size[rt]; lv[rt]+=v*size[rt]*(size[rt]+1)/2; rv[rt]+=v*size[rt]*(size[rt]+1)/2; ans[rt]+=v*size[rt]*(size[rt]+1)*(size[rt]+2)/6; a[rt]+=v; } void flip(int rt) { swap(ls,rs); swap(lv[rt],rv[rt]); r[rt]^=1; } void pushup(int rt) { size[rt]=size[ls]+size[rs]+1; sum[rt]=sum[ls]+sum[rs]+val[rt]; lv[rt]=lv[ls]+lv[rs]+(val[rt]+sum[rs])*(size[ls]+1); rv[rt]=rv[ls]+rv[rs]+(val[rt]+sum[ls])*(size[rs]+1); ans[rt]=ans[ls]+ans[rs]+val[rt]*(size[ls]+1)*(size[rs]+1)+lv[ls]*(size[rs]+1)+rv[rs]*(size[ls]+1); } void pushdown(int rt) { if(r[rt]) { r[rt]^=1; flip(ls); flip(rs); } if(a[rt]) { add(ls,a[rt]); add(rs,a[rt]); a[rt]=0; } } void rotate(int rt) { int fa=f[rt]; int anc=f[fa]; int k=get(rt); if(!is_root(fa)) { s[anc][get(fa)]=rt; } s[fa][k]=s[rt][k^1]; f[s[fa][k]]=fa; s[rt][k^1]=fa; f[fa]=rt; f[rt]=anc; pushup(fa); pushup(rt); } void splay(int rt) { int top=0; st[++top]=rt; for(int i=rt;!is_root(i);i=f[i]) { st[++top]=f[i]; } for(int i=top;i>=1;i--) { pushdown(st[i]); } for(int fa;!is_root(rt);rotate(rt)) { if(!is_root(fa=f[rt])) { rotate(get(fa)==get(rt)?fa:rt); } } } void access(int rt) { for(int x=0;rt;x=rt,rt=f[rt]) { splay(rt); s[rt][1]=x; pushup(rt); } } void reverse(int rt) { access(rt); splay(rt); flip(rt); } void link(int x,int y) { reverse(x); f[x]=y; } void cut(int x,int y) { reverse(x); access(y); splay(y); if(s[x][1]||f[x]!=y) { return ; } s[y][0]=f[x]=0; pushup(y); } void change(int x,int y,ll z) { reverse(x); access(y); splay(y); add(y,z); } int find(int rt) { while(f[rt]) { rt=f[rt]; } return rt; } void split(int x,int y) { reverse(x); access(y); splay(y); } ll gcd(ll x,ll y) { if(y==0) { return x; } return gcd(y,x%y); } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%lld",&val[i]); pushup(i); } for(int i=1;i<n;i++) { scanf("%d%d",&x,&y); link(x,y); } while(m--) { scanf("%d%d%d",&opt,&x,&y); if(opt==1) { if(find(x)==find(y)) { cut(x,y); } } else if(opt==2) { if(find(x)!=find(y)) { link(x,y); } } else if(opt==3) { scanf("%lld",&z); if(find(x)==find(y)) { split(x,y); add(y,z); } } else { if(find(x)==find(y)) { split(x,y); p=ans[y]; q=size[y]*(size[y]+1)/2; res=gcd(p,q); printf("%lld/%lld\n",p/res,q/res); } else { printf("-1\n"); } } } }
BZOJ3091城市旅行——LCT區間信息合並