P3806 【模板】点分治1 (点分治小结 )

传送门

分治点

  既然是分治,我们肯定每一次都要选择一个点,从他开始分治下去。那么考虑我们如何选择这个点呢?我们发现,每一次处理完一个点之后,我们都要递归进它的子树,那么时间复杂度受到它最大的子树的大小的影响。比如,如果原树是一条链,我们选择链首,一路递归下去,时间复杂度毫无疑问是O(n^2)的(那还不如别学了直接打暴力)。所以,我们要让每一次选到的点的最大子树最小。

  实际上,一棵树的最大子树最小的点有一个名称,叫做重心。

来自:https://www.cnblogs.com/bztMinamoto/p/9489473.html

找重心很简单,推荐直接看后面的代码。

对于一颗树,求出任意两点间的距离,两点有三种选择:

  1. 左子树一个点,右子树一个点
  2. 两个点都在左子树
  3. 两个点都在右子树

很明显,第一种我们可以通过两点到根节点的距离之和可以得到两点间的距离。

个人对点分治的理解就是,求出每个根节点左子树中任一点到右子树中任一点的距离(本人蒟蒻,勿喷)。

点分治部分代码:

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;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值