bzoj 3572: [Hnoi2014]世界树

Description

世界树是一棵无比巨大的树,它伸出的枝干构成了整个世界。在这里,生存着各种各样的种族和生灵,他们共同信奉着绝对公正公平的女神艾莉森,在他们的信条里,公平是使世界树能够生生不息、持续运转的根本基石。
世界树的形态可以用一个数学模型来描述:世界树中有n个种族,种族的编号分别从1到n,分别生活在编号为1到n的聚居地上,种族的编号与其聚居地的编号相同。有的聚居地之间有双向的道路相连,道路的长度为1。保证连接的方式会形成一棵树结构,即所有的聚居地之间可以互相到达,并且不会出现环。定义两个聚居地之间的距离为连接他们的道路的长度;例如,若聚居地a和b之间有道路,b和c之间有道路,因为每条道路长度为1而且又不可能出现环,所卧a与c之间的距离为2。
出于对公平的考虑,第i年,世界树的国王需要授权m[i]个种族的聚居地为临时议事处。对于某个种族x(x为种族的编号),如果距离该种族最近的临时议事处为y(y为议事处所在聚居地的编号),则种族x将接受y议事处的管辖(如果有多个临时议事处到该聚居地的距离一样,则y为其中编号最小的临时议事处)。
现在国王想知道,在q年的时间里,每一年完成授权后,当年每个临时议事处将会管理多少个种族(议事处所在的聚居地也将接受该议事处管理)。 现在这个任务交给了以智慧著称的灵长类的你:程序猿。请帮国王完成这个任务吧。

         虚树这个算法很妙啊。

         大概讲一下虚树这个算法吧。

         虚树这个东西是用来处理 多次询问一棵树的某一个点集的某先情况的做法。 简单来说就是把这些询问的点和他们两两的lca求出来,建一棵树来跑dp或者其他算法。而这个两两的lca不会超过询问的点的个数个,这个在算法的实现中会发现的,大概实现如下。

         首先把所有询问的点按照在原树的dfs序排序,我们还需要一个栈来维护从根到当前节点的链。 

         每新枚举到一个点,如果栈为空,直接入栈,否则求它和栈顶的lca。 对于这个lca和 栈顶元素的dfs序有下面三种关系:

        1. 两者相等,说明lca就是栈顶,直接把当前元素入栈。

        2. lca的dfs序是小于栈顶的dfs序, 但是又大于栈顶下面元素的dfs序,这个时候就让栈顶出栈,让lca入栈。因为我们按照dfs排序了,所以此时由于lca不是栈顶,所以栈顶的子树不会再考虑了,所以可以毫不犹豫的让栈顶出栈。

        3.lca的dfs序小于栈顶,也小于栈顶下面那个元素,那就一直pop到出现第一或者第二种两种情况。

       由于存在第二种情况,类似于给栈中间插入了一个点,所以不能在入栈的时候建边,而是应该在一个元素出栈的时候和应该为他父亲的点连边,对于第二种情况就是lca连一条到被弹出栈顶元素的边,对于第三种情况就是栈顶下面的元素连弹出的元素。

        这个就是虚树了。

        对于这个题呢,我们对询问建一棵虚树,对于每个关键点肯定都是被自己管辖,但是由于我们加入到虚树的lca我们是不确定的,所以我们通过dfs1来判断对于每个lca它可以归哪个孩子管辖,再通过dfs2判断对于每个lca它是归儿子管辖近还是归父亲管辖好。

       统计答案的时候,对于每条边u->v,令son为u->v上u的直系儿子,sz[i]为i这个点的原树中的大小,我们二分出一个中点mid,那么显然sz[son]-sz[mid]这一部分是归bel[u],sz[mid]-sz[v]这一部分归bel[v]。 但是这样呢对于每棵树,我们只考虑了它询问部分的子树,所以对于每个点i,我们令g[i]表示它没有考虑的节点个数,那么枚举每个虚树上的边u->v的时候,将g[u]减去sz[son],意为把这部分的子树处理了。 最后统计答案的时候再给ans[bel[i]加上 g[i]即可。

        下附AC代码。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define maxn 300005
using namespace std;
int n,m,tot,top,cnt;
int st[maxn];
int a[maxn],b[maxn],c[maxn],ans[maxn],bel[maxn];
int head[maxn],nex[maxn<<1],to[maxn<<1],dfn[maxn],sz[maxn];
int dep[maxn],anc[maxn][19],dp[maxn];
void add(int x,int y)
{
	if(x==0 || y==0 || x==y) return;
	to[++tot]=y;nex[tot]=head[x];head[x]=tot;
}
int cmp(int i,int j)
{
	return dfn[i]<dfn[j];
}
void dfs(int now,int fa)
{
	for(int i=1;(1<<i)<=dep[now];i++)
		anc[now][i]=(anc[anc[now][i-1]][i-1]);
	sz[now]=1;
	dfn[now]=++tot;
	for(int i=head[now];i;i=nex[i])
	if(to[i]!=fa)
	{
		dep[to[i]]=dep[now]+1;
		anc[to[i]][0]=now;
		dfs(to[i],now);
		sz[now]+=sz[to[i]];
	}
}
int lca(int p,int q)
{
	if(dep[p]<dep[q]) swap(p,q);
	int d=dep[p]-dep[q];
	for(int i=0;i<=18;i++)
		if((1<<i)&d)
			p=anc[p][i];
	if(p==q) return p;
	for(int i=18;i>=0;i--)
		if(anc[p][i]!=anc[q][i])
			p=anc[p][i],q=anc[q][i];
	return anc[p][0];
}
int dis(int p,int q)
{
	return dep[p]+dep[q]-2*dep[lca(p,q)];
}
void dfs1(int now,int fa)
{
	dp[now]=sz[now]; c[++cnt]=now;
//	cerr<<"its "<<now<<endl;
	for(int i=head[now];i;i=nex[i])
	if(to[i]!=fa)
	{
		dfs1(to[i],now);
		if(!bel[to[i]]) continue;
		if(!bel[now]) 
		{
//			cerr<<"+1"<<endl;
			bel[now]=bel[to[i]];
			continue;
		}
		int dis1=dis(bel[now],now),dis2=dis(bel[to[i]],now);
		if(dis2<dis1 || (dis2==dis1 && bel[to[i]]<bel[now])) bel[now]=bel[to[i]];
	}

}
void dfs2(int now,int fa)
{
	for(int i=head[now];i;i=nex[i])
	if(to[i]!=fa)
	{
		if(!bel[to[i]]) 
		{
//			cerr<<"+1"<<endl;
			bel[to[i]]=bel[now];
			continue;
		}
		int dis1=dis(bel[to[i]],to[i]),dis2=dis(bel[now],to[i]);
		if(dis1>dis2 || (dis2==dis1 && bel[now]<bel[to[i]])) bel[to[i]]=bel[now];
		dfs2(to[i],now);
	}
}
void solve(int p,int q)
{
	int son=q,mid=q;
	for(int i=18;i>=0;i--)
		if((1<<i)+dep[p]<dep[son])
			son=anc[son][i];
	dp[p]-=sz[son];
//	cerr<<son<<" "<<mid<<endl;
	if(bel[p]==bel[q]) 
	{
		ans[bel[p]]+=(sz[son]-sz[q]);
		return;
	}
	for(int i=18;i>=0;i--)
	{
		if(dep[p]<dep[anc[mid][i]])
		{
			int nex=anc[mid][i];
			int dis1=dis(nex,bel[p]),dis2=dis(nex,bel[q]);
			if(dis1>dis2 || (dis2==dis1 && bel[p]>bel[q])) mid=nex;
		}
	}
	ans[bel[p]]+=(sz[son]-sz[mid]);
	ans[bel[q]]+=(sz[mid]-sz[q]);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	tot=0;
	dfs(1,0);
	for(int i=1;i<=n;i++) head[i]=0;
	int _;
	scanf("%d",&_);
//	for(int i=1;i<=n;i++)
//	{
//		cout<<"Its "<<dep[i]<<" "<<dfn[i]<<" "<<sz[i]<<" ";
//		for(int j=0;j<=3;j++)
//			cout<<anc[i][j]<<" ";
//		cout<<endl;
//	}
	while(_--)
	{
		tot=0;
		scanf("%d",&m);
		for(int i=1;i<=m;i++)
		scanf("%d",&a[i]),b[i]=a[i],bel[a[i]]=a[i];
		sort(a+1,a+1+m,cmp); 
		if(!bel[1]) st[++top]=1;
		int temp;
//		cout<<st[top]<<endl;
		for(int i=1;i<=m;i++)
		{
			if(!top)
			{
				st[++top]=a[i];
				continue;
			}
			temp=lca(a[i],st[top]);
//			cerr<<st[top]<<" "<<a[i]<<" "<<temp<<endl;
			while(1)
			{
				if(dep[st[top-1]]<=dep[temp])
				{
					add(temp,st[top]); 
					//cerr<<temp<<" "<<st[top]<<endl;
					top--;
					if(st[top]!=temp) st[++top]=temp;
					break;
				}
				add(st[top-1],st[top]);
				top--;
			}
			if(st[top]!=a[i]) st[++top]=a[i];
		}
		while(top>1) add(st[top-1],st[top]),top--;
//		cerr<<tot<<endl;
//		for(int i=1;i<=m;i++)
//			cerr<<bel[a[i]]<<endl;
		tot=0;
		dfs1(1,0);
//		for(int i=1;i<=m;i++)
//			cout<<bel[a[i]]<<endl;
		dfs2(1,0);
//		cerr<<cnt<<endl;
		for(int i=1;i<=cnt;i++)
		{
//			cerr<<bel[c[i]]<<endl;
			for(int j=head[c[i]];j;j=nex[j])
			{
//				cerr<<"+1"<<endl;
				solve(c[i],to[j]);
			}
		}
		for(int i=1;i<=cnt;i++)
			ans[bel[c[i]]]+=dp[c[i]];
		for(int i=1;i<=m;i++)
			printf("%d ",ans[b[i]]);
		printf("\n");
		for(int i=1;i<=cnt;i++)
			ans[c[i]]=dp[c[i]]=head[c[i]]=bel[c[i]]=0;
		top=cnt=tot=0;
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值