点分治

点分治

这是一种统计树上所有路径的算法,分治时间复杂度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 }
AC代码

洛谷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 }
AC代码

洛谷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 }
AC代码

 


loj#2013 点分治 + 线性基合并。

posted @ 2018-09-20 18:01  huyufeifei  阅读(186)  评论(0编辑  收藏  举报
试着放一个广告栏(虽然没有一分钱广告费)

『Flyable Heart 応援中!』 HHG 高苗京铃 闪十PSS 双六 電動伝奇堂 章鱼罐头制作组 はきか 祝姬 星降夜