树的分治 POJ - 1741

                                         树的分治

树的分治分为点分治和边分治,在这里先介绍点分治,边的分治以后补充

点分治:

点分治最重要的一点就是找树的重心,在将点分治之前先介绍什么是树的重心

树的重心的重心也叫的质心。对于一棵树n个节点的无根树,找到一个点,使得把树变成以该点为根的有根树时,最大子树的结点数最小。

只看定义的话对于初学者来说不是很好理解,简单的说,树的重心就是找到树中的一个节点,然后从该节点将树断开。那么这棵树将会分成几颗子树,其中最大的子树节点数最小,那么这个节点就是树的重心

举个例子:

上图从三号节点断开则最大子树的节点树为3,从1号节点断开,则最大子树的节点数为9,从4号节点断开,最大子树的节点数为8,从其他节点断开最大子树的节点数都会比从3号节点断开要大,所以3号节点为这棵树的重心

再求树的重心之前还要补充一个知识,链式前向星,用链式前向星来存图;

链式前向星参考链接 https://blog.csdn.net/qq_40707370/article/details/83827603

存树:

void add(int u,int v,int w)//加边 
{//cnt初始化0
	e[cnt].to=v;
	e[cnt].w=w;
	e[cnt].next=head[u];
	head[u]=cnt++;
}

怎么求一棵树的重心,根据定义,我们要先求出每一课子树的大小,然后根据每一颗子树的大小来求树的重心

 求每一颗子树的大小

void dfs_size(int u,int pre)//遍历整棵树并计算以u为根的树中每颗子树的大小,u表示当前访问的顶点,pre表示上次访问的顶点 
{
	size[u]=1;//以u为根树的大小初始化为1 
	maxv[u]=0;//以u为根树的最大子树的大小,初始化为0
	for(int i=head[u];~i;i=e[i].next)
	{
		int to=e[i].to;//树根u的一个孩子节点 
		if(vis[to]||to==pre)
			continue;
		dfs_size(to,u);
		size[u]+=size[to];//更新以u为根树的大小,
		maxv[u]=max(maxv[u],size[to]);//更新最大子树的大小 
	} 
}

求树的重心:

注意:以u为根会将树分为两部分,所以此时最大子树为这两部分较大的那一个,画个图就明白了

void dfs_root(int r,int u,int pre)//找出以r为根的树的重心,u为当前访问的节点,pre为上次访问的节点 
{
	maxv[u]=max(maxv[u],size[r]-size[u]);//以u作为根节点将一棵树分为两部分,最大子树的大小为这两部分之一
	if(Max>maxv[u])
	{
		Max=maxv[u];//更新最大子树的大小 
		root=u;//以r为根树的重心 
	} 
	for(int i=head[u];~i;i=e[i].next)
	{
		int to=e[i].to;
		if(to==pre||vis[to])
			continue;
		dfs_root(r,to,u);
	} 
}

 分治思想:

例如给你一颗n个顶点的树其中连接顶点a,b的长度为l,问问最短距离不超过k的顶点的对数:

这时我们可以这样做,将树按重心s划分,那么最短距离不超过k的顶点对只可能有两行情况,两个顶点存在以s根根的同一颗子树中,两个顶点不再同一颗子树中,那么如何不重不漏的计算出所有点对呢?可以先计算出所有节点到重心的距离dis,(重心到重心的距离为0)找到所有满足dis[i]+dis[j]<=k的点数ans1,既然是分治思想,那么就是将一颗树分成更小的树,然后求满足题意的点对,这时我们就要减去两个点对在同一颗子树内的情况,即减去两个点对在同一颗子树内的数量ans2,所以当前结果为ans=ans1-ans2,然后在计算子树内符合点对的数量,分析和上述相同,最后的结果是所有的ans累加

经典例题:POJ - 1741

AC代码:

第一次写点分治的代码,看了好长时间才做出来QAQ

#include<iostream>
#include<algorithm>
#include<vector>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=1e5;

struct edge{
	int to;
	int w;
	int next;
}e[maxn];

int n,k,root,Max,ans;
int size[maxn],maxv[maxn],head[maxn],cnt;
vector<int> dis;
bool vis[maxn];

void add(int u,int v,int w)//加边 
{
	e[cnt].to=v;
	e[cnt].w=w;
	e[cnt].next=head[u];
	head[u]=cnt++;
}

void dfs_size(int u,int pre)//遍历整棵树并计算以u为根的树中每颗子树的大小,u表示当前访问的顶点,pre表示上次访问的顶点 
{
	size[u]=1;//以u为根树的大小初始化为1 
	maxv[u]=0;//以u为根树的最大子树的大小,初始化为0
	for(int i=head[u];~i;i=e[i].next)
	{
		int to=e[i].to;//树根u的一个孩子节点 
		if(vis[to]||to==pre)
			continue;
		dfs_size(to,u);
		size[u]+=size[to];//更新以u为根树的大小,
		maxv[u]=max(maxv[u],size[to]);//更新最大子树的大小 
	} 
}

void dfs_root(int r,int u,int pre)//找出以r为根的树的重心,u为当前访问的节点,pre为上次访问的节点 
{
	maxv[u]=max(maxv[u],size[r]-size[u]);//以u作为根节点将一棵树分为两部分,最大子树的大小为这两部分之一
	if(Max>maxv[u])
	{
		Max=maxv[u];//更新最大子树的大小 
		root=u;//以r为根树的重心 
	} 
	for(int i=head[u];~i;i=e[i].next)
	{
		int to=e[i].to;
		if(to==pre||vis[to])
			continue;
		dfs_root(r,to,u);
	} 
}

void dfs_dis(int u,int pre,int dist)//计算每一个顶点到重心的距离 
{
	dis.push_back(dist);//将当前节点到重心的距离存在dis中 
	for(int i=head[u];~i;i=e[i].next)
	{
		int to=e[i].to;
		int w=e[i].w;
		if(vis[to]||to==pre)
			continue;
		dfs_dis(to,u,dist+w);
	} 
} 

int num(int rt,int dist)//计算所有到重心距离小于k的两个点对数 
{
	dis.clear();
	dfs_dis(rt,-1,dist);
	sort(dis.begin(),dis.end());//从小到大排序 
	int i=0,j=dis.size()-1,ans1=0;
	while(i<j)
	{
		while(dis[i]+dis[j]>k&&i<j)
			j--;
		ans1+=j-i;
		i++;
	}
	return ans1;
}
void dfs(int u)//计算以u为根的数中不再同一颗子树的两个点对距离小于k的数目 
{
	Max=n;//最大子树的大小初始化为整棵树的大小
	dfs_size(u,-1);
	dfs_root(u,u,-1);
	int rt=root;//以u为根树的重心 
	ans+=num(rt,0);
	vis[rt]=1;
	for(int i=head[rt];~i;i=e[i].next)
	{
		int to=e[i].to;
		int w=e[i].w;
		if(vis[to])
			continue;
		ans-=num(to,w);//减去两个顶点在同一颗子树的情况 
		dfs(to);//计算儿子节点符合条件的点对数 
	}
} 
int main()
{
	while(cin>>n>>k,n+k)
	{
		int u,v,w;
		cnt=0;
		memset(head,-1,sizeof(head));
		memset(vis,0,sizeof(vis));
		for(int i=1;i<n;i++)
		{
			scanf("%d%d%d",&u,&v,&w);
			add(u,v,w);
			add(v,u,w);
		} 
		ans=0;
		dfs(1);
		cout<<ans<<endl;
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_40707370/article/details/86597506