Noip模拟赛-C(一道环套树的神奇题目)

本文深入探讨了环套树(仙人掌图)的概念及其在算法竞赛中的应用,特别是如何通过构建圆方树来解决涉及点间简单路径数量的问题。文章详细介绍了tarjan算法在寻找环和缩点过程中的运用,以及如何利用LCA(最近公共祖先)算法优化查询效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:http://noi.ac/problem/694

前置知识:一个联通图没有有偶环=它是环套树或者树=仙人掌=没有2个环共用相邻的边(over)

为啥呢:因为若是2个环有相邻的边,那么就存在一个大环的边数=奇+奇-2*重复的边,显然这是一个偶数(over)

环套树的一些性质:m=n(自己想去)所以对于一个奇环森林就可以做到每个点配对一个边。

好,现在考虑题目问的东西。求x,y之间的简单路径。而对于x,y来说,只要经过一个环,他们之间的简单路径总是就*2(自己想)

而在树上的时候显然2个点之间的最短路径只有一条。

所以我们要求的东西就是这2个点之间有多少环。

这里介绍一个神奇的算法(只有在环套树的时候能用啊)

我们要构建一个方圆树,圆点就是正常的点。方点是我们把一个环上所有的点都连在一个新建的点上。所以我们要找到就是方点的个数。(其实这是一个类似缩点的过程,noip2018Day2 T1旅行 也可以这样写,它是一个简化的环套树,不过没必要。。。)

无向图缩点我也懒得讲了。。。自己想去。。。(参见tarjan算法)

现在找环=找方点。考虑lca,用w数组存一下每个点到根的方点的个数。再用lca回答一下询问,做一下2的次幂,over。

#include <bits/stdc++.h>

using namespace std;
/*
这里是一个环套树 

*/
const int maxn=200005;
const int mo=19491001;
int n,m,Q,head[maxn],nxt[maxn],v[maxn],w[maxn];//表示根到他有几个方点 
int dfn[maxn],low[maxn],pa[maxn],vis[maxn],d[maxn],f[maxn][31]; 
vector<int> q[maxn]; 
int indexs=0;int N=0;
void add_edge(int x,int y)
{
	N++;
	v[N]=y;nxt[N]=head[x];head[x]=N;
}
void tarjan(int x)
{
	dfn[x]=low[x]=++indexs;
	for(int i=head[x];i!=-1;i=nxt[i])
	{
		if(v[i]==pa[x]) continue;
		if(!dfn[v[i]])
		{
			pa[v[i]]=x;
			tarjan(v[i]);
			if(low[v[i]]>dfn[x]) q[x].push_back(v[i]);
			//v[i]到不了x的父亲(没有环) 把树边相连 
			low[x]=min(low[x],low[v[i]]);
		}
		else low[x]=min(low[x],dfn[v[i]]);
	}
	for(int i=head[x];i!=-1;i=nxt[i])
	{
		int z=x;
		if(v[i]==pa[x]) continue;
		if(dfn[v[i]]<dfn[x])//v[i]是x的祖先 
		{
			w[++n]=1;
			while(z!=v[i])
			{
				q[n].push_back(z);z=pa[z];	//把从x到v[i]中所有的点都搞到新建的方点里面
				//相当于一个环 
			}
			q[n].push_back(v[i]);
		}
	}
	//啥意思大概是所点啥的吧(搞成圆方树?) 
}
void dfs(int x,int fa)
{
	w[x]+=w[fa];dfn[x]=++indexs;f[x][0]=fa;
	for(int i=1;i<18;i++) f[x][i]=f[f[x][i-1]][i-1];
	for(int i=head[x];i!=-1;i=nxt[i]) if(v[i]!=fa) dfs(v[i],x);
}
inline int lca(int x,int y) {
    if(x==y) return x;
    if(dfn[x]<dfn[y]) swap(x,y);
    for(int i=17;~i;i--)
	if(f[x][i]&&dfn[f[x][i]]>dfn[y]) x=f[x][i];
    return f[x][0];
}
int main()
{
	memset(head,-1,sizeof(head)); 
	scanf("%d%d%d",&n,&m,&Q);
	for(int i=1;i<=m;i++)
	{
		int x,y;scanf("%d%d",&x,&y);
		add_edge(x,y);add_edge(y,x);
	}
	tarjan(1);
	memset(head,-1,sizeof(head));N=0;
	for(int i=1;i<=n;i++)
		for(int j=0;j<q[i].size();j++) 
			if(vis[q[i][j]]!=i)
			{
				vis[q[i][j]]=i;
				add_edge(q[i][j],i);add_edge(i,q[i][j]);
			}
	/*
	1-n的q中存的应该是能帮助i走到祖先的点q[i][j]
	这个地方已经构造成一棵树了 (圆方树) 
	i与q[i][j]相连(其实上面已经做过了这里只是判重而已) 
	*/
	dfs(1,0);
	for(int i=d[0]=1;i<=n;i++) d[i]=2*d[i-1]%mo;
    while(Q--) {
	int x,y;scanf("%d%d",&x,&y);int z=lca(x,y);
	printf("%d\n",d[w[x]+w[y]-w[z]-w[f[z][0]]]);
    }
	return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值