题目链接: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;
}