树分治-点分治入门

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_37632935/article/details/82871307

poj1741

题意:给你一颗有边权的树,询问满足i点到j点距离不大于k的二元组个数。

点分治的入门题目。

前置技能:

树的重心:即以这个点为根,那么所有的子树(不算整个树自身)的大小都不超过整个树大小的一半。在对树进行分治的时候可以避免N^2的极端复杂度(从退化链的一端出发),保证NlogN的复杂度。

会求以当前树为根节点的子树中节点个数,会求以当前节点为根的子树中所有节点到当前节点的距离。

下面开始开始介绍关于树的点分治问题。

我们可以先求出当前节点为根的所有节点到当前节点的距离存入dis数组,然后从小到大排个序。我们要求的答案就是经过当前根节点的且(dis[i]+dis[j])<=k的二元组有多少个,用尺取法我们可以得到当前子树中有多少二元组满足(dis[i]+dis[j])<=k,但这样求的答案会算上有些没有经过根节点的那些答案。我们需要在减去那些没经过root的答案。

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <math.h>
#include <string.h>
using namespace std;
const int MAXN=50005;
int n,m,k;
struct node{
	int to,next,l;
}e[MAXN*2];
int head[MAXN],tot;
void addedge(int u,int v,int l)
{
	e[tot].l=l;e[tot].to=v;e[tot].next=head[u];head[u]=tot++;
}
int root,num,ans,min_root;
int vis[MAXN],son[MAXN],max_son[MAXN];
int dis[MAXN];
void init()
{
	tot=0;ans=0;memset(head,-1,sizeof(head));memset(vis,0,sizeof(vis));
}
void dfs_size(int u,int fa)//每个节点为根的子树的节点数量
{
	son[u]=1;
	max_son[u]=0;
	for(int i=head[u];~i;i=e[i].next)
	{
		int v=e[i].to;
		if(vis[v]||v==fa) continue;
		dfs_size(v,u);
		son[u]+=son[v];
		if(max_son[u]<son[v]) max_son[u]=son[v];
	}
}
void dfs_root(int r,int u,int fa)		//寻找重心 
{
	max_son[u]=max(max_son[u],son[r]-son[u]);
	if(max_son[u]<min_root)
	{
		min_root=max_son[u];
		root=u;
	}
	for(int i=head[u];~i;i=e[i].next)
	{
		int v=e[i].to;
		if(v==fa||vis[v]) continue;
		dfs_root(r,v,u);
	}
	return ;
}
void dfs_dis(int u,int d,int fa)		//每个节点到根节点的距离 
{
	dis[num++]=d;
	for(int i=head[u];~i;i=e[i].next)
	{
		int v=e[i].to;
		if(vis[v]||v==fa) 
			continue;
		dfs_dis(v,d+e[i].l,u);
	}
	return ;
}
int cal(int u,int d)
{
	int res=0;
	num=0;
	dfs_dis(u,d,-1);
	sort(dis,dis+num);
	int i=0,j=num-1;
	while(i<j)
	{
		while(i<j&&dis[i]+dis[j]>k) j--;
		res+=j-i;
		i++;
	}
	return res;
}
int dfs(int u)
{
	min_root=n;
	dfs_size(u,-1); 
	dfs_root(u,u,-1);
	ans+=cal(root,0);
	vis[root]=1;
	for(int i=head[root];~i;i=e[i].next)
	{
		int v=e[i].to;
		if(!vis[v])
		{
			ans-=cal(v,e[i].l);
			dfs(v);
		}
	}
}
int main()
{
	while(scanf("%d%d",&n,&k)!=EOF)
	{
		if(n==0||k==0) break;init();
		for(int i=0;i<n-1;i++)
		{
			int u,v,l;
			scanf("%d%d%d",&u,&v,&l);
			addedge(u,v,l);
			addedge(v,u,l);
		}
		dfs(1);
		printf("%d\n",ans);
	}
	return 0;
}

HDU-5977,依然是点分治,不过把cal函数和dfs_dis函数改了一下,其他的基本一致。

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <math.h>
#include <string.h>
using namespace std;
//普通读入优化
void read(int &x)
{
    int f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    x*=f;
}

typedef long long ll;
const int MAXN=5e5 + 10;
int n,m,k;
struct node{
	int to,next;
}e[MAXN*2];
int head[MAXN],tot;
void addedge(int u,int v)
{
	e[tot].to=v;e[tot].next=head[u];head[u]=tot++;
}
int root,num;ll ans;int min_root;
int vis[MAXN],son[MAXN],max_son[MAXN];
int dis[MAXN];
void init()
{
	tot=0;ans=0;memset(head,-1,sizeof(head));memset(vis,0,sizeof(vis));
}
void dfs_size(int u,int fa)//每个节点为根的子树的节点数量
{
	son[u]=1;
	max_son[u]=0;
	for(int i=head[u];~i;i=e[i].next)
	{
		int v=e[i].to;
		if(vis[v]||v==fa) continue;
		dfs_size(v,u);
		son[u]+=son[v];
		if(max_son[u]<son[v]) max_son[u]=son[v];
	}
}
void dfs_root(int r,int u,int fa)		//寻找重心 
{
	max_son[u]=max(max_son[u],son[r]-son[u]);
	if(max_son[u]<min_root)
	{
		min_root=max_son[u];
		root=u;
	}
	for(int i=head[u];~i;i=e[i].next)
	{
		int v=e[i].to;
		if(v==fa||vis[v]) continue;
		dfs_root(r,v,u);
	}
	return ;
}
int a[MAXN],Hash[1200];				//注意Hash数组别开太大,不然会T。 
void dfs_dis(int u,int fa,int s)		//每个节点到根节点的距离 
{
	dis[num++]=s;
	for(int i=head[u];~i;i=e[i].next)
	{
		int v=e[i].to;
		if(vis[v]||v==fa) 
			continue;
		dfs_dis(v,u,s|(1<<a[v]));
	}
	return ;
}
ll cal(int u,int d)
{
	ll res=0;
	num=0;
	dfs_dis(u,-1,d);
	memset(Hash,0,sizeof(Hash));
	for(int i=0;i<num;i++) Hash[dis[i]]++;
	for(int i=0;i<num;i++)
	{
		Hash[dis[i]]--;
		res+=Hash[(1<<k)-1];
		for(int s0=dis[i];s0;s0=(s0-1)&dis[i])	//枚举子集 
		{
			res+=Hash[((1<<k)-1)^s0];
		}
		Hash[dis[i]]++;
	}
	return res;
}

int dfs(int u)
{
	min_root=n;
	dfs_size(u,-1); 
	dfs_root(u,u,-1);
	ans+=cal(root,(1<<a[root]));
	vis[root]=1; int p=1<<a[root];		//dfs(v)时root会改变,所以要先记录下root。 
	for(int i=head[root];~i;i=e[i].next)
	{
		int v=e[i].to;
		if(!vis[v])
		{
			ans-=cal(v,(p|(1<<a[v])));
			dfs(v);
		}
	}
}

int main()
{
    while(scanf("%d%d", &n, &k)==2)
	{
		if(n==0||k==0) break;init();
		for(int i=1;i<=n;i++) {
		read(a[i]);a[i]--;
		}
		for(int i=1;i<n;i++)
		{
			int u,v;read(u);read(v);addedge(u,v);addedge(v,u);
		}
		if(k==1)
		{
			printf("%d\n",n*n);continue;
		}
		dfs(1);
		printf("%lld\n",ans);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_37632935/article/details/82871307