算法笔记-点分治

设根节点是 r o o t root root,路径在 r o o t root root树中的路径分为两种,一种是经过根节点的路径,另一种是不经过根节点路径。前一种路径可以拆分成两条端点是 r o o t root root的路径,后一种路径完全位于 r o o t root root的子树中,递归处理子树中的路径。

每一层的递归过程都需要处理所有节点,当树是一条链的时候,时间复杂度为 O ( n 2 ) O(n^2) O(n2), 但如果选择子树的重心作为根节点,时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

例题:点分治1

对于当前以 r t rt rt为根节点的树,计算并记录 r t rt rt到所有子节点的距离。对于每个询问 k k k,判断是否有两个点的距离和为 k k k。在计算完 r t rt rt子树后,清空记录的距离。、

  • 树上最长的一条路径长度可能是1e8,存路径的时候数组有可能会越界,询问的时候 k − d i s k-dis kdis有可能为负值,数组也会越界
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
const int maxn = 1e4+10;
const int maxm = 1e4+10;
const int maxk = 1e7+10;
const int INF = 0x3f3f3f3f;
inline int read(){//读入优化 
	char ch;
	int sign = 1;
	while((ch=getchar())<'0'||ch>'9')
		if(ch == '-')
			sign = -1;
	int res = ch-48;
	while((ch=getchar())>='0'&&ch<='9')
		res = res*10+ch-48;
	return res*sign;
}
int head[maxn], tot;
int siz[maxn], fa[maxn], mxson[maxn], tmp[maxn], dis[maxn];
int n, m, rt, sum, cnt;
int q[105];
bool vis[maxn], ans[105], exist[maxk];
struct edge{
	int to, nxt, w;
}e[maxm << 1];
void add(int a, int b, int c){
	e[++tot].nxt = head[a];
	e[tot].to = b;
	e[tot].w = c;
	head[a] = tot;
}
void getrt(int u, int f){
	siz[u] = 1; mxson[u] = 0;
	for(int i = head[u]; i; i = e[i].nxt){
		int v = e[i].to;
		if(v == f || vis[v])
			continue;
		getrt(v, u);
		siz[u] += siz[v];
		if(siz[v] > mxson[u])
			mxson[u] = siz[v];
	}
	mxson[u] = max(mxson[u], sum-siz[u]);
	if(mxson[u] < mxson[rt])
		rt = u;
}
void getdis(int u, int f){
	tmp[cnt++] = dis[u];
	for(int i = head[u]; i; i = e[i].nxt){
		int v = e[i].to;
		if(v == f || vis[v])
			continue;
		dis[v] = dis[u] + e[i].w;
		getdis(v, u);
	}
}
void solve(int u){
	queue<int> que;
	for(int i = head[u]; i; i = e[i].nxt){
		int v = e[i].to;
		if(vis[v])
			continue;
		cnt = 0;
		dis[v] = e[i].w;
		getdis(v, u);
		for(int j = 0; j < cnt; j++)
			for(int k = 0; k < m; k++)
				if(q[k] >= tmp[j])
					ans[k] |= exist[q[k]-tmp[j]];
		for(int j = 0; j < cnt; j++){
			if(tmp[j] > maxk)
				continue;
			que.push(tmp[j]);
			exist[tmp[j]] = 1;
		}
	}
	while(!que.empty()){		
		exist[que.front()] = 0;
		que.pop();
	}
}
void divide(int u){
	vis[u] = exist[0] = true;
	solve(u);
	for(int i = head[u]; i; i = e[i].nxt){
		int v = e[i].to;
		if(vis[v])
			continue;
		mxson[rt = 0] = sum = siz[v];
		getrt(v, 0);
		getrt(rt, 0);
		divide(rt);
	}
}
int main(){
	n = read(), m = read();
	for(int i = 1; i < n; i++){
		int a, b, c;
		a = read(), b = read(), c = read();
		add(a, b, c);
		add(b, a, c); 
	}
	for(int i = 0; i < m; i++)
		q[i] = read();
	mxson[0] = sum = n;
	getrt(1, 0);
	getrt(rt, 0);
	divide(rt);
	for(int i = 0; i < m; i++){
		if(ans[i])
			printf("AYE\n");
		else
			printf("NAY\n");
	}
	return 0;
	
	
}



例题:Tree
对于以 r t rt rt为根节点树,处理 r t rt rt到所有节点的距离,计算有多少节点之间的距离小于 k k k,为了保证复杂度,将距离排序后再计算。
如果满足条件的两个节点的 l c a lca lca不是 r t rt rt,需要减去这次对答案的贡献。大佬的博客
时间复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 4e4+10;
const int INF = 0x3f3f3f3f;
typedef long long ll;
int read(){//读入优化 
	char ch;
	int sign = 1;
	while((ch=getchar())<'0'||ch>'9')
		if(ch == '-')
			sign = -1;
	int res = ch-48;
	while((ch=getchar())>='0'&&ch<='9')
		res = res*10+ch-48;
	return res*sign;
}
struct edge{
	int to, nxt, w;
}e[maxn<<1];
int head[maxn], tot;
void add(int a, int b, int c){
	e[++tot].nxt = head[a];
	e[tot].to = b;
	e[tot].w = c;
	head[a] = tot;
}
int siz[maxn], mxson[maxn], dis[maxn], q[maxn];
bool vis[maxn];
int root, cnt, n, k, sum;
ll ans;
void getroot(int u, int fa){
	siz[u] = 1; mxson[u] = 0;
	for(int i = head[u]; i; i = e[i].nxt){
		int v = e[i].to;
		if(vis[v] || v == fa)
			continue;
		getroot(v, u);
		siz[u] += siz[v];
		mxson[u] = max(siz[v], mxson[u]);
	}
	mxson[u] = max(mxson[u], sum - siz[u]);
	if(mxson[u] < mxson[root])
		root = u;
}

void getdis(int u, int fa){
	q[++cnt] = dis[u];
	for(int i = head[u]; i; i = e[i].nxt){
		int v = e[i].to;
		if(vis[v] || v == fa)
			continue;
		dis[v] = dis[u] + e[i].w;
		getdis(v, u);
	}
}
ll solve(int u, int val){
	cnt = 0;
	dis[u] = val;
	getdis(u, 0);
	ll res = 0, l = 1;
	sort(q+1, q+1+cnt);
	int r = cnt;
	while(l < r){
		if(q[l] + q[r] <= k)
			res += r-l, l++;
		else
			r--;
	}
	return res;
}
void divide(int u){
	ans += solve(u, 0);
	vis[u] = 1;
	for(int i = head[u]; i; i = e[i].nxt){
		int v = e[i].to;
		if(vis[v])
			continue;
		ans -= solve(v, e[i].w);
		sum = siz[v];
		root = 0;
		getroot(v, 0);
		divide(root);
	}
}
int main(){
	n = read();
	int a, b, c;
	for(int i = 1; i <= n-1; i++){
		a = read(), b = read(), c = read();
		add(a, b, c);
		add(b, a, c);
	} 
	k = read();
	sum = n;
	mxson[0] = n;
	getroot(1, 0);
	divide(root);
	printf("%lld", ans);
	return 0;
}



例题:聪聪可可
对于以 r t rt rt为根节点的树,处理 r t rt rt到这棵树上所有子节点的距离,按照模3的余数对路径分类,分别为 s u m 1 , s u m 2 , s u m 3 sum_1, sum_2, sum_3 sum1,sum2,sum3,路径长度为3的倍数的路径数目为
2 ∗ s u m 1 ∗ s u m 2 + s u m 3 ∗ ( s u m 3 − 1 ) + s u m 3 2*sum_1*sum_2 + sum_3*(sum_3-1)+sum_3 2sum1sum2+sum3(sum31)+sum3
符合条件的路径数目除以总的路径数目,他们两个有可能在同一个点,所以总的路径数目为 n 2 n^2 n2

#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 2e4+10;
const int INF = 0x3f3f3f3f;
inline int read(){//读入优化 
	char ch;
	int sign = 1;
	while((ch=getchar())<'0'||ch>'9')
		if(ch == '-')
			sign = -1;
	int res = ch-48;
	while((ch=getchar())>='0'&&ch<='9')
		res = res*10+ch-48;
	return res*sign;
}
struct edge{
	int to, nxt, w;
}e[maxn<<2];
int head[maxn], tot;
int s, root, ans, n;
int siz[maxn], dis[maxn], mxson[maxn];
int sum[3];
bool vis[maxn];
void add(int a, int b, int c){
	e[++tot].nxt = head[a];
	e[tot].to = b;
	e[tot].w = c;
	head[a] = tot;
}
int gcd(int a, int b){
	if(b == 0)
		return a;
	else
		return gcd(b, a%b);
}
void getroot(int u, int fa){
	siz[u] = 1; mxson[u] = 0;
	for(int i = head[u]; i; i = e[i].nxt){
		int v = e[i].to;
		if(v == fa || vis[v])
			continue;
		getroot(v, u);
		siz[u] += siz[v];
		mxson[u] = max(siz[v], mxson[u]);
	}
	mxson[u] = max(mxson[u], s - siz[u]);
	if(mxson[u] < mxson[root])
		root = u;
}
void getdis(int u, int fa){
	sum[dis[u]]++;
	for(int i = head[u]; i; i = e[i].nxt){
		int v = e[i].to;
		if(v == fa || vis[v])
			continue;
		dis[v] = (dis[u]+e[i].w) % 3;
		getdis(v, u);
	}
}
int solve(int u, int val){
	sum[0] = sum[1] = sum[2] = 0;
	dis[u] = val%3;
	getdis(u, 0);
	return sum[1]*sum[2]*2+sum[0]*sum[0];
}
void divide(int u){
	ans += solve(u, 0);
	vis[u] = 1;
	for(int i = head[u]; i; i = e[i].nxt){
		int v = e[i].to;
		if(vis[v])
			continue;
		ans -= solve(v, e[i].w);
		root = 0;
		s = siz[v];
		getroot(v, 0);
		divide(root);
	}
}
int main(){
	n = read();
	int a, b, c;
	for(int i = 1; i < n; i++){
		a = read(), b = read(), c = read();
		add(a, b, c);
		add(b, a, c);
	}
	s = mxson[0] = n;
	root = 0;
	getroot(1, 0);
	divide(root);
	int t = gcd(ans, n*n);
	printf("%d/%d", ans/t, n*n/t);
	return 0;
}

https://oi-wiki.org/graph/tree-divide/#_1
https://blog.csdn.net/qq_39553725/article/details/77542223

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值