点分治
点分治
这是一种统计树上所有路径的算法,分治时间复杂度nlogn
首先对于本子树选择一个根(重心),然后统计所有经过根的路径,然后分治每个子树。
有一个端点为根的路径和单个点的路径需要特殊统计。
点分治不仅可以统计所有路径,还能把对路径的询问离线下来计算。
注意div分治的时候siz要重置。
算法框架大概是:
1.找根
2.进行统计,看情况需要1/2次DFS,还要记录一些东西。
3.清空,对每个子树进行点分治。
来两道例题:
洛谷P2634 聪聪可可 求树上有多少边权和为3的倍数的路径。
统计部分:
对于一端为根的路径,假设先存在一个0子树即可。
每次DFS一个子树,把路径%3统计起来。
与前面子树的结果相乘相加,然后并入前面子树。
1 #include <cstdio> 2 #include <algorithm> 3 #define say(a) printf(#a), printf(" = %d \n", a) 4 const int N = 20010, INF = 0x3f3f3f3f; 5 6 struct Edge { 7 int nex, v, len; 8 }edge[N << 1]; int top; 9 10 int e[N], d[N], fr[N], siz[N], fa[N]; 11 bool del[N]; 12 int n_, root_, small, ans, sum[3], sum_[3]; 13 14 inline void add(int x, int y, int z) { 15 edge[++top].v = y; 16 edge[top].len = z; 17 edge[top].nex = e[x]; 18 e[x] = top; 19 return; 20 } 21 22 int getroot(int x, int f) { 23 siz[x] = 1; 24 int large = 0; 25 for(int i = e[x]; i; i = edge[i].nex) { 26 int y = edge[i].v; 27 if(y == f || del[y]) { 28 continue; 29 } 30 int temp = getroot(y, x); 31 siz[x] += temp; 32 large = std::max(large, temp); 33 } 34 if(std::max(large, n_ - siz[x]) < small) { 35 small = std::max(large, n_ - siz[x]); 36 root_ = x; 37 } 38 return siz[x]; 39 } 40 41 void DFS_1(int x, int f) { 42 fa[x] = f; 43 for(int i = e[x]; i; i = edge[i].nex) { 44 int y = edge[i].v; 45 if(y == fa[x] || del[y]) { 46 continue; 47 } 48 d[y] = d[x] + edge[i].len; 49 sum_[d[y] % 3]++; 50 //fr[y] = fr[x]; 51 DFS_1(y, x); 52 } 53 return; 54 } 55 56 void poi_div(int root) { 57 sum[0] = sum[1] = sum[2] = 0; 58 sum[0] = 1; 59 small = INF; 60 getroot(root, 0); 61 root = root_; 62 for(int i = e[root]; i; i = edge[i].nex) { 63 int y = edge[i].v; 64 if(del[y]) { 65 continue; 66 } 67 d[y] = edge[i].len; 68 sum_[d[y] % 3]++; 69 fr[y] = y; 70 DFS_1(y, root); /// get d[], fa[] 71 ans += sum_[0] * sum[0]; 72 ans += sum_[1] * sum[2]; 73 ans += sum_[2] * sum[1]; 74 75 for(int i = 0; i < 3; i++) { 76 sum[i] += sum_[i]; 77 sum_[i] = 0; 78 } 79 } 80 del[root] = 1; 81 for(int i = e[root]; i; i = edge[i].nex) { 82 int y = edge[i].v; 83 if(del[y]) { 84 continue; 85 } 86 n_ = siz[y]; 87 poi_div(y); 88 } 89 return; 90 } 91 92 int gcd(int x, int y) { 93 if(!y) { 94 return x; 95 } 96 return gcd(y, x % y); 97 } 98 99 int main() { 100 int n; 101 scanf("%d", &n); 102 for(int i = 1, x, y, z; i < n; i++) { 103 scanf("%d%d%d", &x, &y, &z); 104 add(x, y, z); 105 add(y, x, z); 106 } 107 n_ = n; 108 poi_div(1); 109 110 ans <<= 1; 111 ans += n; 112 int g = gcd(ans, n * n); 113 printf("%d/%d", ans / g, n * n / g); 114 115 return 0; 116 }
洛谷P4178 Tree 求树上有多少边权和不超过k的路径
这个是另一种统计方法:
首先扫描每一个子树,得出深度d和属于哪个子树fr
把所有子节点放入数组t中,按照d排序。
使用两个指针l = 1,r = size(t)
在t[l + 1, r]里有cnt[i]个i子树中的节点。
然后每次把l++,调整r
以t[l]为一个端点且t[l]的d较小的路径个数就是r - l - cnt[fr[t[l]]]
最后记得清空t和cnt
1 #include <cstdio> 2 #include <algorithm> 3 #include <cstring> 4 #define say(a) printf(#a), printf(" = %d \n", a) 5 typedef long long LL; 6 7 const int N = 40005, INF = 0x3f3f3f3f; 8 9 struct Edge { 10 int v, nex, len; 11 }edge[N << 1]; int top; 12 13 int e[N], fa[N], fr[N], siz[N], cnt[N], t[N]; 14 bool del[N]; 15 int n_, root_, small, tt, ans; 16 LL d[N], k; 17 18 inline bool cmp(int x, int y) { 19 return d[x] < d[y]; 20 } 21 22 inline void add(int x, int y, int z) { 23 edge[++top].v = y; 24 edge[top].nex = e[x]; 25 edge[top].len = z; 26 e[x] = top; 27 return; 28 } 29 30 int getroot(int x, int f) { 31 siz[x] = 1; 32 int large = 0; 33 for(int i = e[x]; i; i = edge[i].nex) { 34 int y = edge[i].v; 35 if(y == f || del[y]) { 36 continue; 37 } 38 int temp = getroot(y, x); 39 siz[x] += temp; 40 large = std::max(large, temp); 41 } 42 if(std::max(large, n_ - siz[x]) < small) { 43 small = std::max(large, n_ - siz[x]); 44 root_ = x; 45 } 46 return siz[x]; 47 } 48 49 void DFS_1(int x, int f) { 50 fa[x] = f; 51 t[++tt] = x; 52 cnt[fr[x]]++; 53 for(int i = e[x]; i; i = edge[i].nex) { 54 int y = edge[i].v; 55 if(y == fa[x] || del[y]) { 56 continue; 57 } 58 fr[y] = fr[x]; 59 d[y] = d[x] + edge[i].len; 60 ans += (d[y] <= k); 61 DFS_1(y, x); 62 } 63 return; 64 } 65 66 void poi_div(int x) { 67 small = INF; 68 getroot(x, 0); 69 x = root_; 70 for(int i = e[x]; i; i = edge[i].nex) { 71 int y = edge[i].v; 72 if(del[y]) { 73 continue; 74 } 75 fr[y] = y; 76 d[y] = edge[i].len; 77 ans += (d[y] <= k); /// way that one point is root 78 DFS_1(y, x); /// get d[] fa[] fr[] insert t[] 79 } 80 //cal 81 std::sort(t + 1, t + tt + 1, cmp); 82 int l = 1, r = tt; /// cnt[] [l + 1, r] 83 cnt[fr[t[1]]]--; 84 while(l < r) { 85 while(l < r && d[t[l]] + d[t[r]] > k) { 86 cnt[fr[t[r]]]--; 87 r--; 88 } 89 if(l == r) { 90 break; 91 } 92 ans += (r - l - cnt[fr[t[l]]]); /// cal 93 l++; 94 cnt[fr[t[l]]]--; 95 } 96 cnt[fr[t[l]]] = 0; /// here !! 97 /// pay attentions !! above may error 98 tt = 0; 99 del[x] = 1; 100 for(int i = e[x]; i; i = edge[i].nex) { 101 int y = edge[i].v; 102 if(del[y]) { 103 continue; 104 } 105 n_ = siz[y]; 106 poi_div(y); 107 } 108 109 return; 110 } 111 112 int main() { 113 int n; 114 scanf("%d", &n); 115 for(int i = 1, x, y, z; i < n; i++) { 116 scanf("%d%d%d", &x, &y, &z); 117 add(x, y, z); 118 add(y, x, z); 119 } 120 scanf("%lld", &k); 121 122 n_ = n; 123 poi_div(1); 124 125 printf("%d", ans); 126 127 return 0; 128 }
洛谷P3806 【模板】点分治1
求100次树上是否存在长度为K的路径
我一开始想的是离线,因为感觉上跑100次点分治肯定会超时。
结果还真是求100次...
每次点分治的时候开一个set,然后看set里面有没有
好,点分治的套路大概就这样。
又来了一道题......考场上没想到怎么统计答案就写的暴力。
题意:给你个树,每条边有长度和权值。求长度不超过L的链的最大权值和。n<=30000
一眼点分治......
统计答案是这样的:搞个set,然后每个子树的所有点都提取出来搞个结构体数组,然后排序。
发现对于len单增的那个子树内的节点,set里的决策集合是单增的。
于是维护该决策集合内的权值最大值即可。
反正就是那样......看代码。
1 #include <cstdio> 2 #include <set> 3 #include <algorithm> 4 5 const int N = 30010, INF = 0x3f3f3f3f; 6 7 struct Edge { 8 int nex, v; 9 int len, val; 10 }edge[N << 1]; int top; 11 12 struct Node { 13 int d, val; 14 Node(int dis = 0, int value = 0) { 15 d = dis; 16 val = value; 17 } 18 inline bool operator <(const Node &w) const { 19 return d < w.d; 20 } 21 }node[N]; int t; 22 23 int L, ans; 24 int siz[N], num, small, root; // point divided 25 int e[N]; 26 bool del[N]; 27 28 std::set<Node> st; 29 30 inline bool cmp(const Node &a, const Node &b) { 31 return a.d > b.d; 32 } 33 34 inline void add(int x, int y, int z, int w) { 35 top++; 36 edge[top].v = y; 37 edge[top].len = z; 38 edge[top].val = w; 39 edge[top].nex = e[x]; 40 e[x] = top; 41 return; 42 } 43 44 void getroot(int x, int f) { 45 siz[x] = 1; 46 int large = -1; 47 for(int i = e[x]; i; i = edge[i].nex) { 48 int y = edge[i].v; 49 if(y == f || del[y]) { 50 continue; 51 } 52 getroot(y, x); 53 large = std::max(large, siz[y]); 54 siz[x] += siz[y]; 55 } 56 large = std::max(large, num - siz[x]); 57 if(large < small) { 58 small = large; 59 root = x; 60 } 61 return; 62 } 63 64 void DFS_1(int x, int f, int s, int v) { 65 if(s > L) { 66 return; 67 } 68 ans = std::max(ans, v); 69 node[++t] = Node(s, v); 70 for(int i = e[x]; i; i = edge[i].nex) { 71 int y = edge[i].v; 72 if(del[y] || y == f) { 73 continue; 74 } 75 DFS_1(y, x, s + edge[i].len, v + edge[i].val); 76 } 77 return; 78 } 79 80 void poi_div(int x) { 81 small = INF; 82 getroot(x, 0); 83 x = root; 84 85 st.clear(); 86 for(int i = e[x]; i; i = edge[i].nex) { 87 int y = edge[i].v; 88 if(del[y]) { 89 continue; 90 } 91 92 t = 0; 93 DFS_1(y, x, edge[i].len, edge[i].val); 94 95 // cal ans 96 std::sort(node + 1, node + t + 1, cmp); 97 std::set<Node>::iterator it = st.begin(); 98 int large = 0; // error -1 99 for(int i = 1; i <= t; i++) { 100 while(it != st.end() && (*it).d + node[i].d <= L) { 101 large = std::max(large, (*it).val); 102 it++; 103 } 104 ans = std::max(ans, large + node[i].val); 105 } 106 107 //merge y 108 for(int i = 1; i <= t; i++) { 109 st.insert(node[i]); 110 } 111 } 112 113 del[x] = 1; 114 for(int i = e[x]; i; i = edge[i].nex) { 115 int y = edge[i].v; 116 if(del[y]) { 117 continue; 118 } 119 num = siz[y]; 120 poi_div(y); 121 } 122 return; 123 } 124 125 int main() { 126 int n, m; 127 scanf("%d%d%d", &n, &m, &L); 128 for(int i = 1, x, y, z, w; i <= m; i++) { 129 scanf("%d%d%d%d", &x, &y, &z, &w); 130 add(x, y, z, w); 131 add(y, x, z, w); 132 } 133 134 num = n; 135 poi_div(1); 136 137 printf("%d", ans); 138 return 0; 139 }
loj#2013 点分治 + 线性基合并。