【圖論】支配樹

定義

支配樹一般用來求有向圖必經點問題,
即:給定起點S,問對於每個點i,S到i的必經點有哪些;

點i在支配樹上父親就是距離它最近的必經點,
顯然的,必經點是具有一定傳遞性的,所以對於點i,S到i的所有必經點,就是支配樹上i到根的路徑上的所有點。

建樹

首先,對於一棵樹,它的支配樹就是它本身;

對於一個DAG,它的支配樹也很好求,
先排拓撲序,對於點i,它在支配樹上的父親就是所有能到達它的點在支配樹上的LCA,這個挺好理解的,

那麼對於一般的有向圖呢?

先定義一些東西:dfn[x]表示x這個點的dfs序,

定義點y是點x的半支配點,當且僅當存在一條從y到x的路徑y,v1,v2,v3....,vk,xy,v_1,v_2,v_3....,v_k,x,滿足dfn[vi]<dfn[x](1ik)dfn[v_i]<dfn[x](1\leq i \leq k),這條路徑就叫它支配路徑好了(捂臉);
設semi[x]表示x的所有半支配點中,dfn最小的那個; 顯然最小的是唯一的
(顯然的,每個點semi至少是它在dfs樹上的父親)
定義idom[x]表示點x在支配樹上的父親(距離x最近的必經點)

通過定義可以得出一個結論:
結論1:刪除原圖中的非樹邊(dfs樹),再添加邊(semi[x],x),支配樹依舊不變

證明:
我們考慮對於每個點x,刪除它的其他入邊並加上(semi[x],x)後,對全局的影響,
對於一個點,它的入邊分成3類:

  1. 樹邊
  2. 父親連過來(dfn小於當前點)
  3. 後代/橫叉邊(dfn大於當前點)

第一種邊得到了直接的保留,不用管,

對於第二種,第三種邊,這些邊存在的意義就是提供一條路徑,使得不經過 x到根路徑上的樹邊 也能從x的某一個祖先走過來,
而又因爲如果y在semi[x]到x在dfs樹的路徑上(不含semi[x]),那麼y一定不是x的必經點,也就是隻要semi[x]提供了路徑就夠了,其他y沒有必要再提供,

有了這個結論就可以把一般圖轉爲DAG再用上面的方法了,複雜度:O(nlog(n))O(n\log(n))

要注意:semi不等於idom,通過semi我們只能確認idom一定不再semi[x]~x這段樹上路徑(不含semi[x])上,對於idom是否等於semi還需要分類討論。

關於semi怎麼求請繼續往下看


接下來是O(n)O(n)的方法
既然我們都把semi求出來了,考慮如何用semi推出idom,

有一個顯然的結論:
結論2:對於點x,它到idom[x]的dfs樹上路徑中,一定不存在在路徑上的點y,使得 dfn[idom[y]]<dfn[idom[x]];

由此可以比較自然的再拋出一個結論:
結論3:設y爲semi[x]~x樹上路徑上(不含semi[x]),dfn[idom[y]]最小的點,則idom[x]=(dfn[semi[x]]<dfn[idom[y]])?semi[x]:idom[y];
這個證明挺顯然的,因爲已經轉成DAG了,歸納即可;

這個東西同理得:semi[y]是路徑上所有的semi中dfn最小的

這樣我們就可以在得知semi後快速求出idom;


那麼現在問題來了,怎麼求出semi?

我們設bestxbest_x表示點x的入邊中,出發點dfn最小的點,(直接連向x的點中dfn最小的)

按dfn排序從大到小做,
我們做的過程相當於每次加入一個點x,dfn[x]一定是當前全局最小的,相當於加入了一個子樹根,
(下面爲了方便表示用min表示“取dfn最小的點”
那麼semi[x]=min{best[y](yx)}semi[x]=\min\{best[y](y可以到達x)\}(注意這裏只能走已經加入了的點)
同樣的semi[x]=min{semi[y](yx)}semi[x]=\min\{semi[y](y可以到達x)\}
同樣的semi[x]=min{semi[y](yx)}semi[x]=\min\{semi[y](y可以只經過一條樹邊到達x)\}

也就是說,我們每加入一個新點x,設y爲可以通過返祖邊直接到達x的點,z爲y~x樹上路徑上的點,則semi[x]=min{semi[z]}semi[x]=\min\{semi[z]\}

這個可以通過帶權並查集,利用其路徑壓縮的特性來做,(壓縮每個點到當前根的路徑)

求出了semi那怎麼求idom呢,
我們只需要找到x~semi[x]的路徑上(不含semi[x])semi的dfn最小的點即可,

這個相當於詢問一條祖先後代鏈上權值最小的點是哪個,
和上面一樣的方案,只不過主體反過來了,我們把詢問掛到dfn較小的那個點上,每次依舊是用帶權並查集維護,

Code

這裏給出的是CodeChef上GRAPHCNT這一題的標,
這題就是個版子題。

#include <cstdio>
#include <algorithm>
#include <iostream>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define efo(i,q,I) for(int i=A[I][q];i;i=B[i][0])
#define min(q,w) ((q)>(w)?(w):(q))
#define max(q,w) ((q)<(w)?(w):(q))
using namespace std;
typedef long long LL;
const int N=200500;
int read(int &n)
{
	char ch=' ';int q=0,w=1;
	for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
	if(ch=='-')w=-1,ch=getchar();
	for(;ch>='0' && ch<='9';ch=getchar())q=q*10+ch-48;n=q*w;return n;
}
int m,n;
LL ans;
int B[15*N][2],A[4][N],B0;
int dfn[N],dfn0,Dzx[N],Fa[N];
int g[N],gv[N];
int semi[N],idom[N];
bool z[N];
int gf(int q){return g[q]==q?q:(gf(g[q]),(gv[q]=(dfn[semi[gv[g[q]]]]<dfn[semi[gv[q]]])?gv[g[q]]:gv[q]),g[q]=g[g[q]]);}
void link(int q,int w,int I)
{
	B[++B0][0]=A[I][q],A[I][q]=B0,B[B0][1]=w;
}
void tarjan(int q)
{
	z[q]=1;Dzx[dfn[q]=++dfn0]=q;
	efo(i,q,0)
	{
		if(!z[B[i][1]])tarjan(B[i][1]),Fa[B[i][1]]=q;
		if(dfn[semi[B[i][1]]]>dfn[q])semi[B[i][1]]=q;
	}
}
void Gsemi()
{
	fo(i,1,n)g[i]=gv[i]=i;
	fod(I,dfn0,1)
	{
		int q=Dzx[I];
		efo(i,q,1)if(dfn[B[i][1]]>dfn[q])
		{
			gf(B[i][1]);
			if(dfn[semi[gv[B[i][1]]]]<dfn[semi[q]])semi[q]=semi[gv[B[i][1]]];
		}
		gv[q]=q;
		link(semi[q],q,2);
		efo(i,q,2)if((gf(B[i][1]),semi[gv[B[i][1]]])==q)idom[B[i][1]]=q;
		else idom[B[i][1]]=gv[B[i][1]];

		efo(i,q,0)if(dfn[B[i][1]]>dfn[q]&&g[B[i][1]]==B[i][1])g[B[i][1]]=q;
	}
	fo(i,2,dfn0)
	{
		int q=Dzx[i];
		if(idom[q]!=semi[q])idom[q]=idom[idom[q]];
	}
}
int Si[N];
int dfs(int q,int fa)
{
	Si[q]=1;
	efo(i,q,3)if(B[i][1]!=fa)Si[q]+=dfs(B[i][1],q);
	return Si[q];
}
int main()
{
	int q,w;
	read(n),read(m);
	fo(i,1,m)read(q),read(w),link(q,w,0),link(w,q,1);
	fo(i,1,n)semi[i]=i;
	tarjan(1);
	Gsemi();
	idom[1]=0;
	fo(i,1,n)if(idom[i])link(idom[i],i,3);
	
	dfs(1,0);
	ans=q=0;
	efo(i,1,3)
	{
		ans+=(LL)q*Si[B[i][1]];
		q+=Si[B[i][1]];
	}
	printf("%lld\n",ans+Si[1]-1);
	return 0;
}

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