杭电2019多校第三场 HDU-6604 Blow up the city(支配树+必经点个数)

链接:http://acm.hdu.edu.cn/showproblem.php?pid=6604

题意:T组样例。第一行给出n、m(n个点,m条单向边)。接下来m行描述这条边(给出u、v)u->v。再给一个q,表明q个询问,每个询问给出a、b。询问有多少点,那它去掉后,a、b中至少一个点不能到达出度为0的点。(也就是a或b到达所有出度为0的点的必经点的个数,在反向图中也就是所有入度为0的点到a或b的必经点的个数。)。题目说了该图是一个DAG。

思路:一个图是DAG的话,求必经点的个数就简单多了。我们逆向思考,如果我们建反向图,并且把反向图中入度为0的点(也就是原图中出度为0的点)和一个超级源点0(即支配树中的根节点)相连,那么这个有可能为森林的图,就变成了一棵树。这棵树就是我们需要的支配树。那么一个询问(a、b)的答案,就转化为求a、b的必经点的个数。由支配树的性质,一个点的必经点的个数就是其在支配树上到根节点的距离,那么答案就是deep[a]+deep[b]-deep[Lca(a,b)]。因此这个题,我们不必把支配树建出来,只需要记下支配树中每个点的深度和倍增找LCA所需要的信息即可。(具体参考https://blog.csdn.net/birdmanqin/article/details/97816603  很类似)

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5+10;
int deep[N],f[N][20],in[N],id[N],q[N],he,ta;
int n,m,root,num,u,v,Q;
vector<int> g[N];
void Init()
{
	memset(f,0,sizeof(f));
	for(int i=0;i<=n;i++)
		in[i]=0,g[i].clear();
	return ;	
} 
void TopoSort()
{	
	//把根节点的深度设为0,方便计算 
	deep[root]=0;
	//根节点的父亲还是根节点 
	f[root][0]=root; 
	num=0;
	he=ta=0;
 	for(int i=1;i<=n;i++)
 		if(!in[i])	q[ta++]=i;	
	while(he!=ta)
	{
		u=q[he++];
		//记下每个拓扑序的点 
		id[++num]=u;
		for(int i=0;i<g[u].size();i++)
		{
			v=g[u][i];
			in[v]--;
			if(!in[v])
			{
				q[ta++]=v;
			}
		}
	}
	return;
}
//倍增求LCA 
int Lca(int x,int y)
{
	if(deep[x]<deep[y])
		swap(x,y);
	int diff=deep[x]-deep[y];
	for(int i=0;i<=17;i++)//从小往大找 
		if(diff&(1<<i)) x=f[x][i];
	if(x==y) return x;
	for(int i=17;i>=0;i--)
		if(f[x][i]!=f[y][i])
			x=f[x][i],y=f[y][i];
	return f[x][0];
}
int main(void)
{
	//cout<<(1<<17)<<endl;
	root=0;
	int t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		Init();
		//说反向图是为了好理解还是正向建图,
		//因为后面要用到已经与其相连并且在支配树的点的LCA
		//来确定每个点在支配树中的父亲。 
		 
		for(int i=1;i<=m;i++)
		{
			scanf("%d%d",&u,&v);
			g[u].push_back(v);
			in[v]++;	
		}	
		TopoSort();
		//拓扑排序倒序建支配树 
		for(int i=n;i>=1;i--)
		{	
			v=id[i];
			//也就是出度为0的点,直接与根节点0相连 
			if(!g[v].size())
			{
				f[v][0]=root;
				deep[v]=deep[root]+1;
				continue;
			}
			u=g[v][0]; 
			for(int j=1;j<g[v].size();j++)
				u=Lca(u,g[v][j]);
			deep[v]=deep[u]+1;
			f[v][0]=u;
			for(int i=1;i<=17;i++)
				f[v][i]=f[f[v][i-1]][i-1];
		}
		scanf("%d",&Q);
		while(Q--)
		{
			scanf("%d%d",&u,&v);
			//因为我的根节点的深度为0,算出来刚好是答案 
			printf("%d\n",deep[u]+deep[v]-deep[Lca(u,v)]);			
		}		
	}
	
	return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值