淺談長鏈剖分
阿新 • • 發佈:2020-08-24
用途:優化轉移時間複雜度只和深度有關的樹形DP
思想:定義重兒子為深度最大的兒子,每次用O(1)的時間繼承來自重兒子的資訊,然後暴力合併親兒子,均攤時間複雜度O(n),均攤空間複雜度O(n)。
證明:
時間複雜度:
定義長鏈為最長的全是由重兒子組成的鏈,則長鏈的頂點一定是一個輕兒子。
對於每個點,只存在於一條長鏈上,每條長鏈只會合併一次,需要O(長鏈長度)的時間,並且合併後根節點的資訊個數(根節點深度個)不會發生變化。
所以可以大膽地把時間均勻分配到每個輕鏈上的節點,均攤O(1),
最後子樹除去過根節點長鏈外所有節點均攤O(1),而長鏈上節點沒有合併沒有時間消耗,可看出一條鏈參與接下來的流程。
故時間為O(n)。
空間複雜度:
對於每條長鏈上的節點,可以通過模擬指標的方式將資訊以O(d)的大小儲存在頂點。每個點只屬於一條鏈,故總需空間O(n)
證畢
先想DP,3個點距離相同,一定是分別到兩個點的Lca距離相同。
所以令f[u][i]表示在u的子樹中深度為i的節點的個數,g[u][i]表示在u的子樹中還需i的距離的點對數目,轉移如下
void dfs(int fa,int u) { f[u][0]=1; for(int e=he[u];e;e=nxt[e]) { int v=to[e];if(v!=fa) { dfs(u,v); for(int j=1;j<=n;j++) { ans+=g[v][j]*f[u][j-1]+g[u][j]*f[v][j-1]; } for(int j=1;j<=n;j++) { g[u][j]+=g[v][j+1]+(ll)f[u][j]*f[v][j-1]; f[u][j]+=f[v][j-1]; } g[u][0]+=g[v][1]; } } }
用長鏈剖分進行優化(陣列可以往大里開)誰叫我不會指標呢
#include<bits/stdc++.h> #define ll long long using namespace std; const int N=5005,M=2e5+5; int n,cnt,id,to[N<<1],nxt[N<<1],he[N],dep[N],son[N],sf[N],sg[N]; ll t[M],ans; inline void add(int u,int v) { to[++cnt]=v,nxt[cnt]=he[u],he[u]=cnt; } void dfs(int fa,int u) { if(son[u]) { sf[son[u]]=sf[u]+1; sg[son[u]]=sg[u]-1; dfs(u,son[u]); } t[sf[u]]=1,ans+=t[sg[u]]; for(int e=he[u];e;e=nxt[e]) { int v=to[e]; if(v!=fa&&v!=son[u]) { sf[v]=id,id+=dep[v]<<1,sg[v]=id,id+=dep[v]<<1; dfs(u,v); for (int j=0;j<dep[v];++j){ if (j) ans+=t[sf[u]+j-1]*t[sg[v]+j]; ans+=t[sg[u]+j+1]*t[sf[v]+j]; } for (int j=0;j<dep[v];++j){ t[sg[u]+j+1]+=t[sf[u]+j+1]*t[sf[v]+j]; if (j) t[sg[u]+j-1]+=t[sg[v]+j]; t[sf[u]+j+1]+=t[sf[v]+j]; } } } } void dfs1(int fa,int u) { for(int e=he[u];e;e=nxt[e]) { int v=to[e]; if(v!=fa) { dfs1(u,v); if(dep[son[u]]<dep[v]) son[u]=v; } } dep[u]=dep[son[u]]+1; } int main() { scanf("%d",&n); for(int i=1;i<n;i++) { int u,v; scanf("%d%d",&u,&v); add(u,v),add(v,u); } dfs1(0,1); sf[1]=id,id+=dep[1]<<1,sg[1]=id,id+=dep[1]<<1; dfs(0,1); printf("%lld\n",ans); return 0; }