树分治专题

树分治专题

poj 1741

题目要求

找出树中有多少点对 满足dis(u,v)<K

参考AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include<bits/stdc++.h>
using namespace std;
const int maxn=10010;
int N,K;
int ans,root,Max;
struct node{
int v,next,w;
}edge[maxn*2];
int head[maxn],tot;
int size[maxn]; //树的大小
int maxv[maxn]; //最大孩子节点的size
int vis[maxn];
int dis[maxn];
int num;
void init(){
tot=0;
ans=0;
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
}
void add_edge(int u,int v,int w){
edge[tot].v=v;
edge[tot].w=w;
edge[tot].next=head[u];
head[u]=tot++;
}
void dfssize(int u,int fa){ //处理子树的大小
size[u]=1;
maxv[u]=0;
for(int i=head[u];i!=-1;i=edge[i].next){
int v=edge[i].v;
if(v==fa || vis[v]) continue;
dfssize(v,u);
size[u]+=size[v];
if(size[v]>maxv[u]) maxv[u]=size[v];
}
}
void dfsroot(int r,int u,int f){ //找重心
if(size[r]-size[u] > maxv[u]) //size[r]-size[u]是u上面部分的树的尺寸,跟u的最大孩子比,找到最大孩子的最小差值节点
maxv[u]=size[r]-size[u];
if(maxv[u] < Max) Max=maxv[u],root=u;
for(int i=head[u];i!=-1;i=edge[i].next){
int v=edge[i].v;
if(v==f || vis[v]) continue;
dfsroot(r,v,u);
}
}
void dfsdis(int u,int d,int fa){ //求每个点离重心的距离
dis[num++]=d;
for(int i=head[u];i!=-1;i=edge[i].next){
int v=edge[i].v;
if(v!=fa && !vis[v])
dfsdis(v,d+edge[i].w,u);
}
}
int calc(int u,int d){ //计算以u为根的子树中有多少点对的距离小于等于K
int ret=0;
num=0;
dfsdis(u,d,0);
sort(dis,dis+num);
int i=0,j=num-1;
while(i<j){
while(dis[i]+dis[j]>K && i<j) j--;
ret+=j-i;
i++;
}
return ret;
}
void dfs(int u){
Max=N;
dfssize(u,0);
dfsroot(u,u,0);
ans+=calc(root,0);
vis[root]=1;
for(int i=head[root];i!=-1;i=edge[i].next){
int v=edge[i].v;
if(!vis[v]){
ans-=calc(v,edge[i].w);
dfs(v);
}
}
}
int main(){
freopen("input.txt","r",stdin);
while(scanf("%d%d",&N,&K)!=EOF){
if(N==0 && K==0) break;
init();
for(int i=1;i<N;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add_edge(u,v,w);
add_edge(v,u,w);
}
dfs(1);
printf("%d\n",ans);
}
return 0;
}

思路

树分治(点分治)模版题
点分治就是每次找到重心,然后把重心去掉,对分成的每两棵树之间分别统计路径信息(以重心的每个相邻点为根,遍历整棵子树即可得到这个根到每个结点的统计信息)
就可以知道包含这个重心的所有路径的信息,然后对于剩下的路径就是在子树里面进行同样的操作了,直到只剩一个点为止(注意这一个点所构成的路径有时也要处理一下)
边分治就是每次找到一条边,使得删掉这条边后分成的两棵子树大小尽可能平均,然后以删掉的边的两端点为根,分别统计根到两棵树中的每个结点的路径信息,
最后合并算路径,即可得到包含这条边的所有路径的信息,剩下的路径在两棵树中递归处理。

SPOJ-QTREE4

题目要求

给定一棵树,节点有黑白两种颜色,有正负的边权。有两种操作:
1.修改反转某个节点的颜色;
2.询问树上最远的两个白色节点的距离。

参考AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#include<bits/stdc++.h>
using namespace std;
const int MX = 2e5 + 5;
const int MXE = 4e6 + 5;
struct Edge {
int v, w, nxt, pre;
} E[MXE], edge[MXE];
int Head[MX], head[MX], rear, tot, tail[MX];
int mark[MX], sz[MX];
int N, n, cnt, rt, midedge, Max;
void init() {
memset(head, -1, sizeof(head));
tot = 0;
}
void INIT() {
memset(Head, -1, sizeof(Head));
rear = 0;
}
void add(int u, int v, int w) {
edge[tot].v = v;
edge[tot].w = w;
edge[tot].nxt = head[u];
head[u] = tot++;
}
void ADD(int u, int v, int w) {
E[rear].v = v;
E[rear].w = w;
E[rear].nxt = Head[u];
Head[u] = rear++;
}
void Delete(int u, int i) {
if (Head[u] == i) Head[u] = E[i].nxt;
else E[E[i].pre].nxt = E[i].nxt;
if (tail[u] == i) tail[u] = E[i].pre;
else E[E[i].nxt].pre = E[i].pre;
}
//保证每个点的度不超过3
void build(int u, int fa) {
int father = 0;
for (int i = head[u]; ~i; i = edge[i].nxt) {
int v = edge[i].v, w = edge[i].w;
if (v == fa) continue;
if (father == 0) { //还没有增加子节点,直接连上
ADD(u, v, w); ADD(v, u, w);
father = u;
build(v, u);
} else { //已经有一个子节点,则创建一个新节点,把v连在新节点上
mark[++N] = 0;
ADD(N, father, 0); ADD(father, N, 0);
father = N;
ADD(v, father, w); ADD(father, v, w);
build(v, u);
}
}
}
//nxt是下一条边的编号,pre是上一条边的编号
void get_pre() {
memset(tail, -1, sizeof(tail));
for (int i = 1; i <= N; i++) {
for (int j = Head[i]; ~j; j = E[j].nxt) {
E[j].pre = tail[i];
tail[i] = j;
}
}
}
//重建一个图
void rebuild() {
INIT();
N = n;
for (int i = 1; i <= N; i++) mark[i] = 1;
build(1, 0);
get_pre();
init();
}
struct point {
int u, dis;
point() {}
point(int _u, int _dis) {
u = _u; dis = _dis;
}
bool operator<(const point& _A)const {
return dis < _A.dis;
}
};
struct node {
int rt, midlen, ans; //根节点,中心边,答案(最长树链)
int ls, rs; //左右子树编号
priority_queue<point>q;
} T[2*MX];
//搜索每个子树大小
void dfs_size(int u, int fa, int dir) {
add(u, rt, dir);
//如果是白点,则压入根节点rt的队列,dist为到根的距离
//队列中的点用来pt的父亲树的更新,pt节点不需要用到,
//因此T[pt].rt是父亲树的中心边上的点
if (mark[u]) T[rt].q.push(point(u, dir));
sz[u] = 1;
for (int i = Head[u]; ~i; i = E[i].nxt) {
int v = E[i].v, w = E[i].w;
if (v == fa) continue;
dfs_size(v, u, dir + w);
sz[u] += sz[v];
}
}
//找中心边
void dfs_midedge(int u, int code) {
if (max(sz[u], sz[T[rt].rt] - sz[u]) < Max) {
Max = max(sz[u], sz[T[rt].rt] - sz[u]);
midedge = code;
}
for (int i = Head[u]; ~i; i = E[i].nxt) {
int v = E[i].v;
if (i != (code ^ 1)) dfs_midedge(v, i);
}
}
//更新
void PushUP(int rt) {
T[rt].ans = -1;
while (!T[rt].q.empty() && mark[T[rt].q.top().u] == 0) T[rt].q.pop();//弹出黑点
int ls = T[rt].ls, rs = T[rt].rs; //ls为左儿子,rs为右儿子
if (ls == 0 && rs == 0) { //没有左右儿子
if (mark[T[rt].rt])T[rt].ans = 0;
} else {
if (T[ls].ans > T[rt].ans) T[rt].ans = T[ls].ans; //如果左儿子的结果大于右儿子
if (T[rs].ans > T[rt].ans) T[rt].ans = T[rs].ans; //如果右儿子的结果大于左儿子
if (!T[ls].q.empty() && !T[rs].q.empty()) //穿过中心边的
T[rt].ans = max(T[rt].ans, T[ls].q.top().dis + T[rs].q.top().dis + T[rt].midlen);
}
}
void DFS(int id, int u) {
rt = id; Max = N; midedge = -1;
T[id].rt = u;
dfs_size(u, 0, 0);
dfs_midedge(u, -1);
if (~midedge) {
//中心边的左右2点
int p1 = E[midedge].v;
int p2 = E[midedge ^ 1].v;
//中心边长度
T[id].midlen = E[midedge].w;
//左右子树
T[id].ls = ++cnt;
T[id].rs = ++cnt;
//删除中心边
Delete(p1, midedge ^ 1);
Delete(p2, midedge);
DFS(T[id].ls, p1);
DFS(T[id].rs, p2);
}
PushUP(id);
}
void update(int u) {
mark[u] ^= 1;
for (int i = head[u]; ~i; i = edge[i].nxt) {
int v = edge[i].v, w = edge[i].w;
if (mark[u] == 1) T[v].q.push(point(u, w));
PushUP(v);
}
}
int main() {
// freopen("input.txt","r",stdin);
scanf("%d", &n);
init();
for (int i = 1, u, v, w; i < n; i++) {
scanf("%d%d%d", &u, &v, &w);
add(u, v, w); add(v, u, w);
}
rebuild();
DFS(cnt = 1, 1);
char op[2]; int m, x;
scanf("%d", &m);
while (m--) {
scanf("%s", op);
if (op[0] == 'A') {
if (T[1].ans == -1) printf("They have disappeared.\n");
else printf("%d\n", T[1].ans);
} else {
scanf("%d", &x);
update(x);
}
}
return 0;
}

思路

树分治(边分治)

HDU 5977

题目要求

给一棵节点数为n,节点种类为k的无根树,问其中有多少种不同的简单路径,可以满足路径上经过所有k种类型的点?(a->b与b->a算作两条路径,起点与终点也可以相同)

参考AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 5e5 + 10;
int n, k, Max, root;
ll ans;
vector <int> tree[MAXN];
vector <int> sta;
int sz[MAXN], maxv[MAXN], a[MAXN];
ll Hash[1200];
bool vis[MAXN];
void init() {
memset(vis, false, sizeof(vis));
for (int i = 1; i <= n; i++) tree[i].clear();
}
void dfs_size(int u, int pre) {
sz[u] = 1; maxv[u] = 0;
int cnt = tree[u].size();
for (int i = 0; i < cnt; i++) {
int v = tree[u][i];
if (v == pre || vis[v]) continue;
dfs_size(v, u);
sz[u] += sz[v];
maxv[u] = max(maxv[u], sz[v]);
}
}
void dfs_root(int r, int u, int pre) {
maxv[u] = max(maxv[u], sz[r] - sz[u]);
if (Max > maxv[u]) {
Max = maxv[u];
root = u;
}
int cnt = tree[u].size();
for (int i = 0; i < cnt; i++) {
int v = tree[u][i];
if (v == pre || vis[v]) continue;
dfs_root(r, v, u);
}
}
void dfs_sta(int u, int pre, int s) {
sta.push_back(s);
int cnt = tree[u].size();
for (int i = 0; i < cnt; i++) {
int v = tree[u][i];
if (v == pre || vis[v]) continue;
dfs_sta(v, u, s | (1 << a[v]));
}
}
ll cal(int u, int s) {
ll res = 0;
sta.clear(); dfs_sta(u, -1, s);
memset(Hash, 0, sizeof(Hash));
int cnt = sta.size();
for (int i = 0; i < cnt; i++) Hash[sta[i]]++;
for (int i = 0; i < cnt; i++) {
Hash[sta[i]]--;
res += Hash[(1 << k) - 1];
for (int s0 = sta[i]; s0; s0 = (s0 - 1) & sta[i])
res += Hash[((1 << k) - 1) ^ s0];
Hash[sta[i]]++;
}
return res;
}
void dfs(int u) {
Max = n;
dfs_size(u, -1); dfs_root(u, u, -1);
ans += cal(root, (1 << a[root]));
vis[root] = true;
int cnt = tree[root].size(), rt = root;
for (int i = 0; i < cnt; i++) {
int v = tree[rt][i];
if (vis[v]) continue;
ans -= cal(v, (1 << a[rt]) | (1 << a[v]));
dfs(v);
}
}
int main() {
//freopen("input", "r", stdin);
while (scanf("%d%d", &n, &k) == 2) {
init();
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
--a[i];
}
for (int i = 1; i < n; i++) {
int u, v;
scanf("%d%d", &u, &v);
tree[u].push_back(v);
tree[v].push_back(u);
}
if (k == 1) {
printf("%d\n", n * n);
continue;
}
ans = 0;
dfs(1);
printf("%lld\n", ans);
}
return 0;
}

思路

点分治+状压
参考博客

HDU 4918

题目要求

动态修改点权
多次询问到某点距离小于k的点权总共是多少

参考博客

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#include<bits/stdc++.h>
using namespace std;
const int maxn=100010;
const int INF=1000000000;
int N,Q;
int cnt,id[maxn];
int w[maxn];
int pool[maxn*40]; //树状数组缓冲池
int *tail;
int tot,head[maxn];
int root,Max;
int size[maxn],maxv[maxn];
struct BIT
{
int *tree;
int n;
void init(int tot)
{
n=tot;
tree=tail;
tail=tail+tot;
memset(tree,0,sizeof(int)*n);
}
void add(int x,int val)
{
while(x<n)
{
tree[x]+=val;
x+=~x&x+1;
}
}
int getsum(int x)
{
x=min(x,n-1);
int sum=0;
while(x>=0)
{
sum+=tree[x];
x-=~x&x+1;
}
return sum;
}
}bit[maxn<<1];
int fa[maxn];
int vis[maxn];
struct node
{
int v,next;
}edge[maxn*2];;
void init()
{
tot=0;
cnt=0;
tail=pool;
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
}
void add_edge(int u,int v)
{
edge[tot].v=v;
edge[tot].next=head[u];
head[u]=tot++;
}
int dfssize(int u,int fa)
{
size[u]=1;
maxv[u]=0;
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].v;
if(v==fa||vis[v])continue;
dfssize(v,u);
size[u]+=size[v];
if(size[v]>maxv[u])maxv[u]=size[v];
}
return size[u];
}
void dfsroot(int r,int u,int f)
{
if(size[r]-size[u]>maxv[u])
maxv[u]=size[r]-size[u];
if(maxv[u]<Max)Max=maxv[u],root=u;
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].v;
if(v==f||vis[v])continue;
dfsroot(r,v,u);
}
}
int que[maxn];
int dis[maxn];
struct Node
{
int root,dis,subtree;
Node(){}
Node(int a,int b,int c):root(a),subtree(b),dis(c){}
};
vector<Node> V[maxn];
void solve(int u,int root,int subtree)
{
int l,r;
que[l=r=1]=u;
dis[u]=1;
fa[u]=0;
while(l<=r)
{
u=que[l];
V[u].push_back(Node(id[root],subtree,dis[u]));
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].v;
if(vis[v]||v==fa[u])continue;
dis[v]=dis[u]+1;
que[++r]=v;
fa[v]=u;
}
l++;
}
bit[subtree].init(r+1);
for(int i=1;i<=r;i++)
{
int v=que[i];
bit[id[root]].add(dis[v],w[v]);
bit[subtree].add(dis[v],w[v]);
}
}
void dfs(int u)
{
Max=INF;
int tot=0;
tot=dfssize(u,0);
dfsroot(u,u,0);
vis[root]=1;
id[root]=cnt++;
V[root].push_back(Node(id[root],-1,0));
bit[id[root]].init(tot);
bit[id[root]].add(0,w[root]);
for(int i=head[root];i!=-1;i=edge[i].next)
{
int v=edge[i].v;
if(vis[v])continue;
solve(v,root,cnt);
cnt++;
}
for(int i=head[root];i!=-1;i=edge[i].next)
{
int v=edge[i].v;
if(vis[v])continue;
dfs(v);
}
}
int main()
{
// freopen("input.txt","r",stdin);
while(scanf("%d%d",&N,&Q)!=EOF)
{
int u,v;
init();
for(int i=0;i<=N;i++)V[i].clear();
for(int i=1;i<=N;i++)scanf("%d",&w[i]);
for(int i=1;i<N;i++)
{
scanf("%d%d",&u,&v);
add_edge(u,v);
add_edge(v,u);
}
dfs(1);
char op[5];
int x,y;
while(Q--)
{
scanf("%s%d%d",op,&x,&y);
if(op[0]=='!')
{
int cha=y-w[x];
int len=V[x].size();
for(int i=0;i<len;i++)
{
Node tmp=V[x][i];
bit[tmp.root].add(tmp.dis,cha);
if(tmp.subtree!=-1)
bit[tmp.subtree].add(tmp.dis,cha);
}
w[x]+=cha;
}
else
{
int ans=0;
int len=V[x].size();
for(int i=0;i<len;i++)
{
Node tmp=V[x][i];
ans+=bit[tmp.root].getsum(y-tmp.dis);
if(tmp.subtree!=-1)
ans-=bit[tmp.subtree].getsum(y-tmp.dis);
}
printf("%d\n",ans);
}
}
}
return 0;
}

思路

树分治+树状数组

文章目录
  1. 1. 树分治专题
    1. 1.1. poj 1741
      1. 1.1.1. 题目要求
      2. 1.1.2. 参考AC代码
      3. 1.1.3. 思路
    2. 1.2. SPOJ-QTREE4
      1. 1.2.1. 题目要求
      2. 1.2.2. 参考AC代码
      3. 1.2.3. 思路
    3. 1.3. HDU 5977
      1. 1.3.1. 题目要求
      2. 1.3.2. 参考AC代码
      3. 1.3.3. 思路
    4. 1.4. HDU 4918
      1. 1.4.1. 题目要求
      2. 1.4.2. 参考博客
      3. 1.4.3. 思路
|