模板:点分治点分树
文章目錄
- 前言
- 點分治
- 背景
- 解析
- 代碼
- 點分樹
- 情境
- 代碼
- thanks for reading!
所謂點分治,就是把所有的點分開來治
(逃)
(應廣大觀眾要求,開篇廢話改回原風格qwq)
前言
很神奇的算法。
沒有引入任何新的知識,卻把時間復雜度降到了一個很好的等級。
感覺點分治模板本身當成一道題來做也不過分。
對于點分樹,感覺它的另一個名字也很不錯:動態點分治。
通常可以解決一些與樹原形態聯系較小的問題(如距離等)。
點分治
背景
給定一棵樹,求樹上距離不超過k的點對數目
n≤105n\leq10^5n≤105
傳送門
解析
考慮點分治
分治的普遍特征是把總問題分解成若干子問題遞歸求解,再合并答案
本題中,不難想到按照重心把子樹分成若干個聯通塊進行遞歸,這樣劃分次數不會超過log級別
現在的重點就是,如何合并答案
首先各個聯通塊的答案肯定要加起來
剩下還沒統計到的就是 經過重心(設為x) 的合法路徑了
考慮把所有點dfs一遍算出深度,sort一下后利用雙指針就可以線性求出來
比較敏銳的童鞋很顯然會發現我這純粹就是胡說八道,因為這樣還會重復統計到在同一個子樹內的答案
所以我們要把在同一個子樹內的答案容斥掉
代碼實現上,計算答案和容斥部分可以通過傳參的不同用一個函數進行
具體實現很優雅,看代碼應該能更好的理解
代碼
#include<bits/stdc++.h> using namespace std; #define ll long long #define debug(...) fprintf(stderr,__VA_ARGS__) const int N=4e4+100; const int M=2e5+10500; inline ll read(){ll x(0),f(1);char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}return x*f; }int n,m; struct node{int to,nxt,w; }p[N<<1]; int fi[N],cnt; inline void addline(int x,int y,int w){p[++cnt]=(node){y,fi[x],w};fi[x]=cnt;return; }int siz[N],mx[N],dis[N],tot,rt,S,ans; bool vis[N];void find(int x,int fa){siz[x]=1;mx[x]=0;for(int i=fi[x];~i;i=p[i].nxt){int to=p[i].to;if(vis[to]||to==fa) continue;find(to,x);siz[x]+=siz[to];mx[x]=max(mx[x],siz[to]);}mx[x]=max(mx[x],S-siz[x]);if(!rt||mx[x]<mx[rt]) rt=x;return; }void getdis(int x,int fa,int d){dis[++tot]=d;for(int i=fi[x];~i;i=p[i].nxt){int to=p[i].to;if(to==fa||vis[to]) continue;getdis(to,x,d+p[i].w);}return; }int calc(int x,int d){tot=0;getdis(x,0,d); sort(dis+1,dis+1+tot);int l=1,r=tot,res(0);while(l<r){if(dis[l]+dis[r]<=m) res+=r-l,l++;else --r;}return res; }void solve(int x){S=siz[x];rt=0;find(x,0);x=rt;ans+=calc(x,0);vis[x]=1;for(int i=fi[x];~i;i=p[i].nxt){int to=p[i].to;if(vis[to]) continue;ans-=calc(to,p[i].w);}for(int i=fi[x];~i;i=p[i].nxt){int to=p[i].to;if(vis[to]) continue;solve(to);}return; }int main(){ #ifndef ONLINE_JUDGE//freopen("a.in","r",stdin);//freopen("a.out","w",stdout); #endifmemset(fi,-1,sizeof(fi));cnt=-1;n=read();for(int i=1;i<n;i++){int x=read(),y=read(),w=read();addline(x,y,w);addline(y,x,w);}m=read();S=n;find(1,0);siz[rt]=n;solve(rt);printf("%d\n",ans);return 0; } /* 4 4 1 2 3 4 7 */點分樹
情境
給出一棵點帶權邊無權的樹,要求你支持在線回答到某個點距離不超過 kkk 的點對權值和,或者修改某個點對權值。
其實所有的分治算法都形成了一個樹形結構,那么這里我們考慮把之前的點分治的結構顯性的建出來。
具體的,每一層的重心都是下一層重心的父親。
這樣我們就建出來點分樹,它的樹高是 O(log?n)O(\log n)O(logn) 的。
對于每次對 xxx 結點的詢問,我們就先統計 xxx 自己處的信息(距離不超過 kkk 的權值和),然后不停往上跳 fafafa,然后統計 fafafa 的信息(與 fafafa 距離不超過 k?dis(fa,x)k-dis(fa,x)k?dis(fa,x) 的權值和)。
那么對應的,我們就需要每個結點維護兩個數據結構:一個維護自己的信息,一個維護對父親的貢獻。(這是一個很重要的套路!)
具體的說,維護兩個樹狀數組,一個維護點分樹上的子樹內到自己距離為 xxx 的權值和,一個維護點分樹上的子樹內到父親距離為 xxx 的權值和。
然而,我們是無法對每個結點開大小為 O(n)O(n)O(n) 的數組的,所以我們考慮使用 vector,只開到 O(sizx)O(siz_x)O(sizx?) 大小,這樣空間復雜度就是 O(nlog?n)O(n\log n)O(nlogn) 的。
代碼
#include<bits/stdc++.h> using namespace std; #define ll long long #define ull unsigned long long #define OK printf("ok\n") #define debug(...) fprintf(stderr,__VA_ARGS__) inline ll read(){ll x(0),f(1);char c=getchar();while(!isdigit(c)) {if(c=='-')f=-1;c=getchar();}while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}return x*f; } const int N=2e5+100; const int inf=1e9+100; int n,m; struct node{int to,nxt; }p[N<<1]; int fi[N],cnt; inline void addline(int x,int y){p[++cnt]=(node){y,fi[x]};fi[x]=cnt;return; } int dep[N],q[N<<1],tot,pl[N]; void dfs0(int x,int f){dep[x]=dep[f]+1;q[++tot]=x;pl[x]=tot;for(int i=fi[x];~i;i=p[i].nxt){int to=p[i].to;if(to==f) continue;dfs0(to,x);q[++tot]=x;}return; } int Min(int x,int y){return dep[x]<dep[y]?x:y; } int lg[N<<1],mn[N<<1][20],mi[20]; void init(){dfs0(1,0);lg[0]=-1;for(int i=1;i<=tot;i++) lg[i]=lg[i>>1]+1;mi[0]=1;for(int i=1;i<=lg[tot];i++) mi[i]=mi[i-1]<<1;for(int i=1;i<=tot;i++) mn[i][0]=q[i];for(int k=1;k<=lg[tot];k++){for(int i=1;i+mi[k]-1<=tot;i++) mn[i][k]=Min(mn[i][k-1],mn[i+mi[k-1]][k-1]);}return; } inline int Lca(int x,int y){x=pl[x];y=pl[y];if(x>y) swap(x,y);int k=lg[y-x+1];//printf(" k=%d\n",k);return Min(mn[x][k],mn[y-mi[k]+1][k]); } inline int Dis(int x,int y){int lca=Lca(x,y);//printf("(%d %d) lca=%d dis=%d\n",x,y,lca,dep[x]+dep[y]-2*dep[lca]);return dep[x]+dep[y]-2*dep[lca]; } vector<int>f[2][N]; int w[N],top[N]; int rt,mnn,siz[N],S,son[N]; bool vis[N]; int o; void findrt(int x,int f){siz[x]=1;son[x]=0;for(int i=fi[x];~i;i=p[i].nxt){int to=p[i].to;if(to==f||vis[to]) continue;findrt(to,x);siz[x]+=siz[to];son[x]=max(son[x],siz[to]);}son[x]=max(son[x],S-siz[x]);if(mnn>son[x]){mnn=son[x];rt=x;}return; } int tim; int solve(int x,int nS){//if(tim%1000==0) debug("%d\n",x);//printf("??\n");S=nS;mnn=inf;findrt(x,0);x=rt;vis[x]=1;siz[x]=S+1;f[0][x].resize(siz[x]+1);f[1][x].resize(siz[x]+1);for(int i=fi[x];~i;i=p[i].nxt){int to=p[i].to;if(vis[to]) continue;int tmp=solve(to,nS-son[to]);top[tmp]=x;}return x; } void add(int op,int x,int p,int w){++p;for(;p<=siz[x];p+=p&-p){f[op][x][p]+=w;//printf("%d\n",p);}return; } int ask(int op,int x,int p){if(p<0) return 0;++p;p=min(p,siz[x]);int res(0);for(;p;p-=p&-p) res+=f[op][x][p];return res; } void upd(int x,int w){//w:deltafor(int i=x;i;i=top[i]) add(0,i,Dis(x,i),w);for(int i=x;top[i];i=top[i]) add(1,i,Dis(x,top[i]),w);return; } int query(int x,int d){int ans=ask(0,x,d);//printf("ans=%d\n",ans);for(int i=x;top[i];i=top[i]){int nd=d-Dis(x,top[i]);//printf("top=%d nd=%d ans+=%d-%d=%d\n",top[x],)ans+=ask(0,top[i],nd)-ask(1,i,nd);}return ans; } signed main(){ #ifndef ONLINE_JUDGEfreopen("a.in","r",stdin);freopen("a.out","w",stdout); #endifmemset(fi,-1,sizeof(fi));cnt=-1;n=read();m=read();for(int i=1;i<=n;i++) w[i]=read();for(int i=1;i<n;i++){int x=read(),y=read();addline(x,y);addline(y,x);}init(); solve(1,n);/*mnn=inf;S=n;findrt(1,0);mnn=0;findrt(rt,0); solve(rt);*/ //return 0;for(int i=1;i<=n;i++) upd(i,w[i]);//for(int i=1;i<=n;i++) printf("i=%d siz=%d top=%d\n",i,siz[i],top[i]);int lst(0);for(int i=1;i<=m;i++){int op=read(),x=read()^lst,y=read()^lst;if(op==1){upd(x,y-w[x]);w[x]=y;}else{printf("%d\n",lst=query(x,y));}}return 0; } /* 7 1 1 2 3 4 5 6 7 1 2 2 3 2 4 4 5 4 6 1 7 0 7 1 */thanks for reading!
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
- 上一篇: 滴滴打车、滴滴出行怎么预约顺风车、打顺风
- 下一篇: 请问如何选择一个好的路由器家庭用的路由器