Noip模擬賽-C(一道環套樹的神奇題目)

題目鏈接: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;
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章