【Luogu P3806】点分治

【Luogu P3806】点分治

点分治主要用于解决无根树上的一些路径统计问题。

点分治的基本步骤:

  1. 选定一个点作为根,把路径分为两类,一类是经过是根节点的,另一类是不经过根节点的。
  2. dfs处理处这一棵路径的信息。
  3. 运用某些方法(双指针,树状数组,桶)等统计方法,确认答案。
  4. 删除根节点,对子树重复进行递归处理。

显然,第一类路径就是在根节点不同子树的路径,第二类就是位于根节点一棵子树内的路径。

第二类我们递归处理即可,关键是第一类应当如何计算。

对于这题我们有两种方法,计算出答案,分别是双指针和桶。

鉴于数据的特殊性,双指针更合适一些,桶的代码取决于权值的值域,容易RE,改换成平衡树可能会使代码变得复杂。

双指针:

处理出\(dis[i]\),即根节点到\(i\)节点的距离,和\(belong[i]\),即节点\(i\)属于根节点的哪一棵子树。

然后按照把所有节点放进一个数组,按照\(dis[i]\)的大小排序,利用双指针求解即可。

复杂度\(O(nlog_2n)\)

void calc()
{
	for (int i=1;i<=m;i++)//遍历每一个询问进行确认
	{
		int k=rec[i],l=1,r=tot;
		if (ans[i]) continue;
		while (l<r)
		{
			if (dis[a[l]]+dis[a[r]]>k) r--;
			else if (dis[a[l]]+dis[a[r]]<k) l++;
			else if (dis[a[l]]+dis[a[r]]==k&&belong[a[l]]==belong[a[r]])
				{
					if (dis[a[r]]==dis[a[r-1]]) r--;
					else l++;
				}
			else 
			{
				ans[i]=true;
				break;
			}
		}
	}
}

另外一个问题就在于取哪一个点作为根更加合适。

显然,如果我们任意取,当树退化成一条链时,复杂度便会变得不可接受。

证明过程显然,自己想象一下。

那么我们要尽可能的使各个子树平衡,才能让递归层数尽可能少。

显然有一种点符合这个性质,也就是重心。

那么我们每一次取当前子树的重心即可。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
struct data
{
	int to,val,next;
}edge[20005];
const int inf=15000005;
int root,cnt,head[10005],a[10005],tot,belong[10005],ans[10005],rec[105],u,v,w,n,m,size[10005],recsize=0x3f3f3f3f,dis[10005],vis[10005],sum;
void add(int u,int v,int w)
{
	edge[++cnt].to=v;
	edge[cnt].next=head[u];
	edge[cnt].val=w;
	head[u]=cnt;
}
void get_root(int u,int fa)
{
	int maxsize=0;
	size[u]=1;
	for (int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if (v==fa||vis[v]) continue;
		get_root(v,u);
		size[u]+=size[v];
		maxsize=max(size[v],maxsize);
	}
	maxsize=max(maxsize,sum-size[u]);
	if (maxsize<recsize)
	{
		root=u;
		recsize=maxsize;
	}
}
void dfs(int now,int rt,int fa)
{
	a[++tot]=now;
	belong[now]=cnt;
        size[now]=1;
	for (int i=head[now];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if (v==fa||vis[v]) continue;
		if (now==rt) cnt++;
		dis[v]=dis[now]+edge[i].val;
		dfs(v,rt,now);
                size[now]+=size[v];
	}
}
void calc()
{
	for (int i=1;i<=m;i++)
	{
		int k=rec[i],l=1,r=tot;
		if (ans[i]) continue;
		while (l<r)
		{
			if (dis[a[l]]+dis[a[r]]>k) r--;
			else if (dis[a[l]]+dis[a[r]]<k) l++;
			else if (dis[a[l]]+dis[a[r]]==k&&belong[a[l]]==belong[a[r]])
				{
					if (dis[a[r]]==dis[a[r-1]]) r--;
					else l++;
				}
			else 
			{
				ans[i]=true;
				break;
			}
		}
	}
}
bool cmp(int x,int y)
{
	return dis[x]<dis[y];
}
void work()
{
	cnt=0;dis[root]=0;tot=0;vis[root]=true;
	dfs(root,root,0);
	sort(a+1,a+1+tot,cmp);
	calc();
	int t=root;
	for (int i=head[t];i;i=edge[i].next)
	{
		if (vis[edge[i].to]) continue;
		recsize=0x3f3f3f3f;
		sum=size[edge[i].to];
		get_root(edge[i].to,t);
		work();
	}
}
int read()
{
	char c=getchar();int num=0;
	while (c<'0'||c>'9') c=getchar();
	while (c>='0'&&c<='9') {num*=10;num+=c-48;c=getchar();}
	return num;
}
int main()
{
	n=read();m=read();
	for (int i=1;i<n;i++)
	{
		u=read(),v=read(),w=read();
		add(u,v,w);add(v,u,w);
	}
	for (int i=1;i<=m;i++)
	{
		int k;
		k=read();
		rec[i]=k;
	}
        sum=n;
	get_root(1,0);
	work();
	for (int i=1;i<=m;i++)
		printf(ans[i]?"AYE\n":"NAY\n");
	return 0;
}
posted @ 2020-05-18 21:45  Nanjo  阅读(173)  评论(5编辑  收藏  举报