树分治
参考IOI2009《分治算法在树的路径中的应用》
1. 点分治
算法框架
void getroot(int x, int fa) { sz[x] = 1, f[x] = 0; for(int i = 0; i < E[x].size(); i++) { int y = E[x][i].to; if(y == fa || vis[y]) continue; getroot(y, x); sz[x] += sz[y]; f[x] = max(f[x], sz[y]); } f[x] = max(f[x], sum-sz[x]); //sum为整个联通分量的大小,记录去除x后, 剩余联通分量的最大节点数 if(f[x] < f[rt]) rt = x; //更新当前重心 }
void solve(int x) { vis[x] = 1;//将当前点标记 for(int i = 0; i < E[x].size(); i++) { int y = E[x][i].to; if(vis[y]) continue ; sum = sz[y];//初始化sum为当前联通分量的大小 getroot(x, rt = 0);//找连通块的重心 solve(rt);//递归处理 } vis[x] = 0; } int main() { build();//建树 sum = f[0] = n;//初始化sum为当前联通分量的大小以及f[0], n个点 getroot(1, rt = 0);//找重心 solve(rt);//rt为重心, 点分治 }
2. 边分治
树中任意点的度数为D。当D为常数时,基于边分治的递归最坏情况下深度为O(logn)。通常D很小, 3~5左右。
=======================================================================================
POJ1741
统计树中有多少对节点距离 <= k,树的点分治+容斥,时间复杂度O(nlognlogn), 内含O(nlogn)排序
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #include<vector> 6 using namespace std; 7 8 const int N = 1e4+5; 9 struct Edge { 10 int to, w; 11 Edge(int to, int w) : to(to), w(w){} 12 Edge(){} 13 bool operator < (const Edge &y) const { 14 return w < y.w; 15 } 16 }; 17 vector<Edge> E[N]; 18 int sz[N], f[N], vis[N]; 19 int sum, rt; 20 21 int d[N]; 22 int tmp[N], tot; 23 24 int n, k, ans; 25 26 void init() {//似乎不用init 27 rt = ans = 0; 28 for(int i = 0; i <= n; i++) 29 E[i].clear(); 30 memset(f, 0, sizeof(f)); 31 memset(vis, 0, sizeof(vis)); 32 memset(sz, 0, sizeof(sz)); 33 memset(d, 0, sizeof(d)); 34 memset(tmp, 0, sizeof(tmp)); 35 } 36 37 38 void getroot(int x, int fa) { 39 sz[x] = 1, f[x] = 0; 40 for(int i = 0; i < E[x].size(); i++) { 41 int y = E[x][i].to; 42 if(y == fa || vis[y]) continue; 43 getroot(y, x); 44 sz[x] += sz[y]; 45 f[x] = max(f[x], sz[y]); 46 } 47 f[x] = max(f[x], sum-sz[x]); //sum为整个联通分量的大小,记录去除x后, 剩余联通分量的最大节点数 48 if(f[x] < f[rt]) rt = x; //更新当前重心 49 } 50 51 void getdeep(int x, int f) { 52 tmp[tot++] = d[x]; 53 for(int i = 0; i < E[x].size(); i++) { 54 int y = E[x][i].to, w = E[x][i].w; 55 if(y == f || vis[y])continue; 56 d[y] = d[x]+w; 57 getdeep(y, x); 58 } 59 } 60 int cal(int x, int now) { 61 d[x] = now, tot = 0; 62 getdeep(x, 0); 63 sort(tmp,tmp+tot); 64 int res = 0, l = 0, r = tot-1; 65 while(l < r) { 66 if(tmp[l]+tmp[r] <= k) 67 res += r-l, l++; 68 else r--; 69 } 70 return res; 71 } 72 void solve(int x) { 73 ans += cal(x, 0); 74 vis[x] = 1; 75 for(int i = 0; i < E[x].size(); i++) { 76 int y = E[x][i].to, w = E[x][i].w; 77 if(vis[y]) continue ; 78 ans -= cal(y, w); 79 sum = sz[y]; 80 getroot(y, rt = 0); 81 solve(rt); 82 } 83 vis[x] = 0; 84 } 85 int main() { 86 while(~scanf("%d%d", &n, &k)) { 87 if(n == 0&&k == 0) break; 88 for(int i = 0; i <= n; i++) E[i].clear(); 89 for(int i = 1; i < n; i++) { 90 int x, y, z; 91 scanf("%d%d%d", &x, &y, &z); 92 E[x].push_back(Edge(y, z)); 93 E[y].push_back(Edge(x, z)); 94 } 95 ans = 0; 96 sum = f[0] = n;//虚拟一个0节点, 但sum != n+1 97 getroot(1, rt = 0); 98 solve(rt); 99 printf("%d\n",ans); 100 } 101 }
树形dp
链分治(树链剖分):修改点/链/子树,链支持快速合并,询问点/链/子树
点分治:树根不固定
DSU:树根固定
诸神对凡人心生艳羡,厌倦天堂。