抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

淀粉质

点分治

可以处理一些有关树上路径的问题,先看题 8️⃣

P3806

首先对于一个点 uu 和它的子树中任意一个点 vvdis(v)dis(v) 表示 vvuu 的路径权值和

考虑开一个桶 u[k]u[k] 记录是否存在 dis(v)=kdis(v) = k

那么不在同一个子树的两个点的贡献就好算了

对于一个存在的 u[w],w<=ku[w],w <= k 如果存在 u[kw]u[k-w] 那么就存在这样一个点对

对于在同一个子树内点的贡献,我们把 uu 删去,把树拆开递归到这些子树中下去求解

肯定是拆成的子树大小越均衡越好,所以每次节点 uu 的选取要讲究一点

每次选取当前树的重心作为 uu ,总时间复杂度 O(nlogn)O(nlogn)

Code
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
#include<bits/stdc++.h>
#define MAXN 10005
#define MAXK 10000005
#define ll long long
#define cjb 1145141919
using namespace std;
namespace F_star
{
int cnte = 0, head[MAXN];
struct edge
{
int to, nxt;
ll dis;
}e[MAXN << 1];
void adde(int u, int v, ll w)
{
e[++cnte].to = v;
e[cnte].dis = w;
e[cnte].nxt = head[u];
head[u] = cnte;
}
}using namespace F_star;
struct query
{
int k, ans;
}que[105];

int n,m;
int siz[MAXN];
int root, sixz, sum;
ll dis[MAXN];
bool vis[MAXN];
bool t[MAXK+5];

void get_zx(int u) //找重心
{
int msiz = 0; // 不能定义成全局变量
siz[u] = 1;
vis[u] = true;
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if(vis[v]) continue;
get_zx(v);
siz[u] += siz[v];
msiz = max(msiz, siz[v]);
}
msiz = max(msiz, sum - siz[u]);
if(msiz < sixz) {sixz = msiz; root = u;}
vis[u] = false;
}

ll sta1[MAXN], top1;
ll sta2[MAXN], top2;
int debug;
void get_dis(int u) //处理子树内个节点到当前根的路径权值
{
if(dis[u] > MAXK) return;
vis[u] = true;
sta1[++top1] = dis[u];
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to; if(vis[v]) continue;
dis[v] = dis[u] + e[i].dis;
get_dis(v);
}
vis[u] = false;
}
void get_ans(int u) //统计答案
{
t[0] = 1;
top2 = 0;
vis[u] = true;
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to; if(vis[v]) continue;
top1 = 0;
dis[v] = e[i].dis; get_dis(v);

for(int j = 1; j <= top1; j++)
{
for(int k = 1; k <= m; k++) //分别统计每个询问
{
if(sta1[j] <= que[k].k)
que[k].ans |= t[que[k].k - sta1[j]];
}
}//先统计在加入贡献,保证统计的是不同子树的两点的贡献

for(int j = 1; j <= top1; j++)
sta2[++top2] = sta1[j], t[sta1[j]] = 1; //加入子树贡献
}
for(int i = 1; i <= top2; i++) //清楚贡献
t[sta2[i]] = 0;
vis[u] = false;
}

void solve(int u) //分治函数
{
get_ans(u);
vis[u] = true;
for(int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if(vis[v]) continue;
sum = siz[v]; sixz = cjb;
get_zx(v);
solve(root);
}
}

int main()
{
scanf("%d%d",&n, &m);
for(int i = 1; i < n; i++)
{
int u, v, w;
scanf("%d%d%d",&u,&v,&w);
adde(u, v, w); adde(v, u, w);
}
for(int i = 1; i <= m; i++)
scanf("%d",&que[i].k);

sum = n; sixz = cjb;
get_zx(1);
solve(root);
for(int i = 1; i <= m; i++)
printf(que[i].ans ? "AYE\n" : "NAY\n");

return 0;
}

P4178 Tree

经典题

给定一个 nn 个节点的树,求树上两点距离小于等于 kk 的点对数量

和板子题相比,询问从是否存在变为了查询点对数量,而且不再等于而是小于等于

小于等于的话,我们对桶求一个前缀和,就可以做到 O(1)O(1) 查询

发现加贡献实际上是单点修改,查询是区间查询,使用数据结构维护桶即可。

时间复杂度 O(nlog2n)O(nlog^2n) 推荐使用树状数组

P2634 国家集训队 聪聪可可

给定一个 nn 个节点的树,求树上两点距离的是 33 的倍数的点对数量

处理权值是 % 3\% \ 3 ,正常维护即可

评论