「學習筆記」點分治重學
好像原來都沒學會
點分治
每次找到重心,然後在子樹裡面統計資訊
最後容斥掉同一個子樹裡面的答案,這裡注意要加(或減) 邊的資訊
點分樹
如果帶修的樹上資訊統計就需要這個東西
其實還是個容斥,就是這裡維護一個數據結構就行了
以 「震波」 一題為例
首先建出來點分樹,然後對於每個點維護兩個資料結構 \(S_1,S_2\) ,這題目中可以考慮是 \(BIT/SGT\)
答案的構成分為在點分樹子樹裡面的和不在的,那麼求的時候跳點分樹每次求就行了
這樣的話會重複一部分,那麼容斥掉
具體而言,維護到 \(x\) 的距離有一個字首和,維護到 \(fa[x]\) 的距離有一個字首和
對於查詢操作,每次暴力跳父親,查詢在子樹裡面的答案和當前點 \(S_1\)
對於修改操作,還是暴力跳父親,每次修改即可
需要動態開點和卡常數
Code
#include<bits/stdc++.h> using namespace std; #define reg register namespace yspm{ inline int read(){ int res=0,f=1; char k; while(!isdigit(k=getchar())) if(k=='-') f=-1; while(isdigit(k)) res=res*10+k-'0',k=getchar(); return res*f; } const int N=1e5+10,M=N*60; struct node{int to,nxt;}e[N<<1]; int f[N],fa[N],mx,head[N],cnt,v[N],sum,rt,n,sz[N],TS,ans,anc; bool vis[N]; inline void add(int u,int v){ e[++cnt].to=v; e[cnt].nxt=head[u]; return head[u]=cnt,void(); } struct LCA{ int fa[N][20],dep[N]; inline void dfs(int x,int fat){ dep[x]=dep[fat]+1; fa[x][0]=fat; for(reg int i=1;(1<<i)<=n;++i) fa[x][i]=fa[fa[x][i-1]][i-1]; for(reg int i=head[x];i;i=e[i].nxt){ int t=e[i].to; if(t==fat) continue; dfs(t,x); } return ; } inline int lca(int x,int y){ if(dep[x]<dep[y]) swap(x,y); for(reg int i=19;~i;--i) if(dep[fa[x][i]]>=dep[y]) x=fa[x][i]; if(x==y) return x; for(reg int i=19;~i;--i) if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i]; return fa[x][0]; } inline int dis(int x,int y){return dep[x]+dep[y]-2*dep[lca(x,y)];} }D;//求樹上的距離 inline void findrt(int x,int fat){ sz[x]=1; f[x]=0; for(reg int i=head[x];i;i=e[i].nxt){ int t=e[i].to; if(vis[t]||t==fat) continue; findrt(t,x); sz[x]+=sz[t]; f[x]=max(f[x],sz[t]); } f[x]=max(f[x],sum-sz[x]); if(f[x]<mx) mx=f[x],rt=x; return ; } inline void updsz(int x,int fat){ sz[x]=1; for(reg int i=head[x];i;i=e[i].nxt){ int t=e[i].to; if(vis[t]||t==fat) continue; updsz(t,x); sz[x]+=sz[t]; } return ; } vector<int> g[N]; inline void build(int x){ vis[x]=1; for(reg int i=head[x];i;i=e[i].nxt){ int t=e[i].to; if(vis[t]) continue; updsz(x,0); mx=2e9+10; sum=sz[t]; findrt(t,0); fa[rt]=x; g[x].push_back(rt); build(rt); } return ; } int rot[N][2]; struct SGT{ int ls[M],rs[M],sum[M],tot; inline void push_up(int x){sum[x]=sum[ls[x]]+sum[rs[x]]; return;} inline void upd(int &p,int l,int r,int pos,int val){ if(!p) p=++tot; if(l==r) return sum[p]+=val,void(); int mid=(l+r)>>1; if(pos<=mid) upd(ls[p],l,mid,pos,val); else upd(rs[p],mid+1,r,pos,val); return push_up(p); } inline int query(int p,int l,int r,int st,int ed){ if(!p) return 0; if(st<=l&&r<=ed) return sum[p]; int mid=(l+r)>>1,res=0; if(st<=mid) res+=query(ls[p],l,mid,st,ed); if(ed>mid) res+=query(rs[p],mid+1,r,st,ed); return res; } }T; inline void upd(int pos,int val){ for(reg int tp=pos;tp;tp=fa[tp]){ T.upd(rot[tp][0],0,sz[tp],D.dis(tp,pos),val); if(fa[tp]) T.upd(rot[tp][1],0,sz[fa[tp]],D.dis(fa[tp],pos),val); } return ; } inline int calc(int pos,int dis){ int res=0; for(reg int t1=pos,t2=0;t1;t2=t1,t1=fa[t1]){ int d=D.dis(pos,t1); if(d>dis) continue; res+=T.query(rot[t1][0],0,sz[t1],0,dis-d); if(t2) res-=T.query(rot[t2][1],0,sz[t1],0,dis-d); }return res; } inline void prework(int x){ sz[x]=1; for(auto t:g[x]) prework(t),sz[x]+=sz[t]; return ; } signed main(){ n=read(); TS=read(); for(reg int i=1;i<=n;++i) v[i]=read(); for(reg int i=1,u,vv;i<n;++i) u=read(),vv=read(),add(u,vv),add(vv,u); mx=2e9+10; sum=n; findrt(1,0); anc=rt; build(rt); D.dfs(1,0); prework(anc); for(reg int i=1;i<=n;++i) upd(i,v[i]); for(reg int x,y;TS--;){ if(read()){ x=read()^ans; y=read()^ans; upd(x,-v[x]); upd(x,v[x]=y); }else{ x=read()^ans,y=read()^ans; printf("%d\n",ans=calc(x,y)); } } return 0; } } signed main(){return yspm::main();}
例題
開店
好象是非常板子的題目誒
這個區間的維護顯然是可以套個資料結構
那麼用線段樹下標就似乎點分子樹裡面 年齡 到當前點的距離和
那麼每次查詢就暴力在點分子樹上面跳父親,然後查詢父親節點的資訊,這裡統計一個數量和一個距離和
考慮容斥,直接數量減掉就完事了,距離和加上那個端點之間的距離乘數目即可
然後這就過去了三個小時,空間顯然是不太行
其實自己知道好像可以用map改一下,空間能優化不少
接著換成vector就過掉了
幻想鄉戰略遊戲
我怕是個弱智
想了好長時間的其他做法,卻又犯了沒有推式子的老毛病
其中有 \(O(nQ)\) 的換根 \(dp\) 和 維護子樹內向最優解的增量差這樣子
正解如下:
先設 \(n_x\) 為 \(x\) 子樹裡面的點權和,\(S_x\) 表示答案
若以 \(x\) 為根,其一個子節點的 \(y\) 滿足 \(S_x-S_y<0\)
那麼有 \((n_x-2\times n_y)\times dis_{x,y}\)
上面是頹的式子,剩下都是想到的
用點分樹維護答案是顯然的,那麼唯一的問題就是找到一個滿足不存在子節點的點權和的二倍大於自己的這樣一個點
直接在點分樹上面找就完事了
這題目上來掛了一發的原因是自己在計算點權和的時候父親的點權應該是直接總的減子樹,當時寫假了
捉迷藏
可以對每個點維護一個 \(multiset\),存進去每個點到其子樹裡面所有點的距離
如果設 \(s_1[x]\) 為最大的點權,\(s_2[x]\) 為次大的點權,這裡預設如果沒有黑點就是 \(-1\)
那麼有一個點的答案就是 \(s_1[x]+s_2[x]\)
把所有點的答案扔進一個 \(multiset\)
如果修改的話就是刪掉一個點或者加入一個點
套上點分治就可以保證空間複雜度有效
但是不難發現這個東西沒有容斥,那麼資料結構裡面再維護個來源
這樣複雜度廢掉了
那麼換成了線段樹維護區間直徑過掉了
剩下紫荊花之戀,大概會附在虛樹筆記的後面