P4178&POJ1741 Tree 点分治模板题

在这里插入图片描述
点分治算法是一种对树上路径统计极其有效的算法。在要求求解树上路径等方面的问题时,我们可以思考能否用点分治解决。
分治是将一个问题拆解成几个子问题的递归解决问题的方法,而在树上的分治受到了树的形状的影响。

在这里插入图片描述
我们看到,加入根节点选取的不合适,造成一棵树的深度过于的大,那么时间复杂度也是极高的,因此我们需要选取合适的点,使得最大子树的最小。而满足这个条件的点我们称之为树的重心,其可以通过一个dfs来获得

int get_root(int u,int fa)//找出树的重心
{
    
    //siz[i]是指以i为根子树的大小,maxson[i]是指以i为根最大的子树的大小
	//SIZE 为当前处理的这棵树的大小 maxx代表已经找到的最大子树的最小值
	siz[u] = 1;maxson[u] = 0;
	for(int i = head[u];i != 0;i = edge[i].next){
    
    
		int v = edge[i].to;
		if(vis[v] || v == fa) continue;
		get_root(v,u);
		siz[u] = siz[u] + siz[v];
		maxson[u] = max(maxson[u],siz[v]);
	}
	maxson[u] = max(maxson[u],SIZE-siz[u]);
	if(maxson[u] < maxx) root = u,maxx = maxson[u];
}

相应的我们也要获得,以当前节点为根节点的子树上的各节点到根节点的距离值

void get_dis(int u,int fa,int d)//从每一棵新建的子树求距离函数
{
    
    
	dis[++num] = d;//d数组保存当前根节点到每一个点的距离
	for(int i = head[u];i;i = edge[i].next){
    
    
		int v = edge[i].to;
		if(vis[v] || v == fa) continue;
		get_dis(v,u,d+edge[i].w);
	}
	return ;
}

接下来就是最为核心的函数了。因为这个题目中我们一开始直接对根节点就行统计时,会有重复的统计,所以我们在接下来的统计中需要消除这些重复的部分。所以solve函数len的数值是会变化的,对dis数组排一个序这样利用双指针的方法,可以求解出有多少对路径满足这个条件。
可以看到,在分治的函数中,初始solve的len不同,因为我们从子树的根节点出发,所以我们需要把根节点到子树根节点的这段距离加上来统计答案

int calculate(int rt,int len)
{
    
    
	num = 0;
	memset(dis,0,sizeof(dis));
	get_dis(rt,0,len);//以当前子树的根节点出发 获得一下路径长度
	sort(dis+1,dis+1+num);
	int L = 1,R = num,res = 0;
	while(L <= R){
    
    //双指针法 确定答案
		if(dis[L] + dis[R] <= k){
    
    
			res += R-L;+
			L++;
		}
		else R--;
	}
	return res;
}

void Divide(int rt)//分治函数 核心部分
{
    
    
	ans = ans + calculate(rt,0);
	vis[rt] = 1;
	for(int i = head[rt];i;i = edge[i].next){
    
    //对没棵子树进行分治
		int v = edge[i].to;
		if(vis[v]) continue;
		ans = ans - calculate(v,edge[i].w);
		SIZE = siz[v];//都重新换一下 以下的信息全部重新赋一个值
		maxx = inf;
		root = 0;
		get_root(v,rt);
		Divide(root);//分治新的根节点
	}
	return ;
}

一般来说,点分治的题目找重心,算距离,分治求解这三个函数具体内容是差不多,关键的还是solve函数(计算函数),根据题目而不同,需要具体分析

完整代码:


Problem: 1741		User: tzteyang777
Memory: 1316K		Time: 844MS
Language: G++		Result: Accepted
Source Code
//#include <bits/stdc++.h>
#include <cstring>
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstdio>

using namespace std;
typedef long long ll;
//点分治
//找到一个节点使其最大子树的大小尽量小 这时分治递归的时间复杂度是最低的
//而这样的节点 就叫做树的重心 可以用一个dfs来求O(n)的时间
const int MAXN = 1e4+7;
#define inf 0x3f3f3f3f
int head[MAXN],maxson[MAXN],vis[MAXN],siz[MAXN],dis[MAXN];
int cnt,SIZE,maxx,num,k,root;
ll ans;
struct Edge
{
    
    
	int to,next,w;
}edge[MAXN<<1];

void addedge(int u,int v,int w)
{
    
    
	edge[++cnt].to = v;
	edge[cnt].w = w;
	edge[cnt].next = head[u];
	head[u] = cnt;
}

int get_root(int u,int fa)//找出树的重心
{
    
    //siz[i]是指以i为根子树的大小,maxson[i]是指以i为根最大的子树的大小
	//SIZE 为当前处理的这棵树的大小 maxx代表已经找到的最大子树的最小值
	siz[u] = 1;maxson[u] = 0;
	for(int i = head[u];i != 0;i = edge[i].next){
    
    
		int v = edge[i].to;
		if(vis[v] || v == fa) continue;
		get_root(v,u);
		siz[u] = siz[u] + siz[v];
		maxson[u] = max(maxson[u],siz[v]);
	}
	maxson[u] = max(maxson[u],SIZE-siz[u]);
	if(maxson[u] < maxx) root = u,maxx = maxson[u];
}

void get_dis(int u,int fa,int d)//从每一棵新建的子树求距离函数
{
    
    
	dis[++num] = d;//d数组保存当前根节点到每一个点的距离
	for(int i = head[u];i;i = edge[i].next){
    
    
		int v = edge[i].to;
		if(vis[v] || v == fa) continue;
		get_dis(v,u,d+edge[i].w);
	}
	return ;
}

int calculate(int rt,int len)
{
    
    
	num = 0;
	memset(dis,0,sizeof(dis));
	get_dis(rt,0,len);
	sort(dis+1,dis+1+num);
	int L = 1,R = num,res = 0;
	while(L <= R){
    
    //双指针法 确定答案
		if(dis[L] + dis[R] <= k){
    
    
			res += R-L;+
			L++;
		}
		else R--;
	}
	return res;
}

void Divide(int rt)//分治函数 核心部分
{
    
    
	ans = ans + calculate(rt,0);
	vis[rt] = 1;
	for(int i = head[rt];i;i = edge[i].next){
    
    //对没棵子树进行分治
		int v = edge[i].to;
		if(vis[v]) continue;
		ans = ans - calculate(v,edge[i].w);
		SIZE = siz[v];//都重新换一下
		maxx = inf;
		root = 0;
		get_root(v,rt);
		Divide(root);//分治新的根节点
	}
	return ;
}

int main()
{
    
    
	int n;
	while(~scanf("%d%d",&n,&k)&&(n&&k)){
    
    
		cnt = 0;
		memset(head,0,sizeof(head));
		for(int i = 1;i < n;i ++){
    
    
			int a,b,c;
			scanf("%d%d%d",&a,&b,&c);
			addedge(a,b,c);
			addedge(b,a,c);
		}
		ans = 0;
		memset(vis,0,sizeof(vis));
		maxx = inf;SIZE = n;
		get_root(1,0);
		Divide(root);
		printf("%lld\n",ans);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_45672411/article/details/108508678
今日推荐