传送门
分治点
既然是分治,我们肯定每一次都要选择一个点,从他开始分治下去。那么考虑我们如何选择这个点呢?我们发现,每一次处理完一个点之后,我们都要递归进它的子树,那么时间复杂度受到它最大的子树的大小的影响。比如,如果原树是一条链,我们选择链首,一路递归下去,时间复杂度毫无疑问是O(
)的(那还不如别学了直接打暴力)。所以,我们要让每一次选到的点的最大子树最小。
实际上,一棵树的最大子树最小的点有一个名称,叫做重心。
来自:https://www.cnblogs.com/bztMinamoto/p/9489473.html
找重心很简单,推荐直接看后面的代码。
对于一颗树,求出任意两点间的距离,两点有三种选择:
- 左子树一个点,右子树一个点
- 两个点都在左子树
- 两个点都在右子树
很明显,第一种我们可以通过两点到根节点的距离之和可以得到两点间的距离。
个人对点分治的理解就是,求出每个根节点左子树中任一点到右子树中任一点的距离(本人蒟蒻,勿喷)。
点分治部分代码:
void getdis(int u, int fa, int w){
d[++cnt] = w;
for(int i = head[u]; i != -1; i = e[i].next){
int v = e[i].to;
if(v == fa || vis[v]) continue;
getdis(v, u, w+e[i].w);
}
}
void pika(int u, int w, int opt){
cnt = 0;
getdis(u, 0, w);
for(int i = 1; i <= cnt; i++){
for(int j = i+1; j <= cnt; j++){
ans[d[i]+d[j]] += opt;
}
}
}
void divide(int u){
vis[u] = 1;
pika(u, 0, 1);
for(int i = head[u]; i != -1; i = e[i].next){
int v = e[i].to;
if(vis[v]) continue;
pika(v, e[i].w, -1);
mi = INF, size = sz[v];
getroot(v, 0);
divide(rt);
}
}
getdis()函数,求每个点到根节点的距离,并记录在d数组里面。
pika()函数,opt == 1统计该棵树中任意两点到根节点的距离之和。opt == -1 去掉属于同一子树间两点到根节点的距离之和
代码:
//https://www.luogu.org/problem/P3806
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+10;
const int INF = 1e9+10;
int n, m, k, ans[10000001];
int head[N], tot;
int rt, mi = INF, sz[N], size;
int d[N], vis[N], cnt = 0;
struct Edge{
int to, next, w;
}e[N<<1];
void addEdge(int u, int v, int w){
e[tot] = Edge{v, head[u], w};
head[u] = tot++;
}
//找重心, 最大子树最小
void getroot(int u, int fa){
sz[u] = 1;
int mxson = 0;
for(int i = head[u]; i != -1; i = e[i].next){
int v = e[i].to;
if(v == fa || vis[v]) continue;
getroot(v, u);
sz[u] += sz[v];
mxson = max(mxson, sz[v]);
}
mxson = max(mxson, size - sz[u]);
if(mxson < mi){
mi = mxson;
rt = u;
}
}
void getdis(int u, int fa, int w){
d[++cnt] = w;
for(int i = head[u]; i != -1; i = e[i].next){
int v = e[i].to;
if(v == fa || vis[v]) continue;
getdis(v, u, w+e[i].w);
}
}
void pika(int u, int w, int opt){
cnt = 0;
getdis(u, 0, w);
for(int i = 1; i <= cnt; i++){
for(int j = i+1; j <= cnt; j++){
ans[d[i]+d[j]] += opt;
}
}
}
void divide(int u){
vis[u] = 1;
pika(u, 0, 1);
for(int i = head[u]; i != -1; i = e[i].next){
int v = e[i].to;
if(vis[v]) continue;
pika(v, e[i].w, -1);
mi = INF, size = sz[v];
getroot(v, 0);
divide(rt);
}
}
int main(){
scanf("%d%d", &n, &m);
tot = 0;
memset(head, -1, sizeof(int)*(n+1));
int u, v, w;
for(int i = 1; i < n; i++){
scanf("%d%d%d", &u, &v, &w);
addEdge(u, v, w);
addEdge(v, u, w);
}
size = n, mi = INF;
getroot(1, 0);
divide(rt);
for(int i = 1; i <= m; i++){
scanf("%d", &k);
if(ans[k]) puts("AYE");
else puts("NAY");
}
return 0;
}
本文深入解析点分治算法及其在树上的应用——树重心。通过实例讲解如何找到树的重心,以及如何利用点分治算法求解树上两点间的距离。代码示例清晰展示了算法实现细节。
&spm=1001.2101.3001.5002&articleId=101203832&d=1&t=3&u=05aa58549fa34b7d89335821a4fc0898)
298

被折叠的 条评论
为什么被折叠?



