「ZJOI2007」 最大半連通子圖 - Tarjan縮點+拓撲排序

題目描述

一個有向圖G=(V,E)G=(V,E)稱爲半連通的(Semi-Connected),如果滿足:u,vV\forall u,v\in V,滿足uvu\to vvuv\to u,即對於圖中任意兩點u,vu,v,存在一條uuvv的有向路徑或者從vvuu的有向路徑。

G=(V,E)G'=(V',E')滿足VVV'\subseteq VEE'EE中所有跟VV'有關的邊,則稱GG'GG的一個導出子圖。

GG'GG的導出子圖,且GG'半連通,則稱GG'GG的半連通子圖。若GG'GG所有半連通子圖中包含節點數最多的,則稱GG'GG的最大半連通子圖。

給定一個有向圖GG,請求出GG的最大半連通子圖擁有的節點數KK,以及不同的最大半連通子圖的數目CC。由於CC可能比較大,僅要求輸出CCXX的餘數。

輸入格式

第一行包含兩個整數N,M,XN,M,XN,MN,M分別表示圖GG的點數與邊數,XX的意義如上文所述接下來MM行,每行兩個正整數a,ba, b,表示一條有向邊(a,b)(a, b)。圖中的每個點將編號爲1,2,3, ,N1,2,3,\cdots,N,保證輸入中同一個(a,b)(a,b)不會出現兩次。

輸出格式

應包含兩行,第一行包含一個整數KK。第二行包含整數Cmod  XC\mod X

數據範圍

對於20%的數據,N18N\le 18
對於60%的數據,N10000N\le 10000
對於100%的數據,N100000,M1000000N\le 100000, M\le 1000000
對於100%的數據,X108X\le 10^8

分析

首先可以知道,對於一條鏈,肯定是半連通圖,從入度爲0的點開始,依次可以遍歷到它之後的點;對於一個強連通圖,一定是半連通圖。然後再考慮原來的問題,若給定的圖是個有向無環圖,則求最大的半連通子圖即爲求最長鏈,個數即爲求最長鏈的個數;因此可以將原圖縮點,形成有向無環圖,在這個新圖裏面進行拓撲排序,在過程中進行Dp,求出以每個點爲結束點的最長鏈及其個數,最後再枚舉新圖中的結束點,找出最長鏈及個數。

需要注意的是,在縮完點建新圖前將每條邊記錄下來,去重,防止個數記錄重複。

代碼

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <iomanip>
#include <queue>
using namespace std;
typedef long long LL;
const int N=100005,M=1000005;
struct Edge {
	int to,nxt;
}e[M*2],e1[M*2];
struct ArrayOfEdge {
	int x,y;
}ee[M];
int h[N],cnt,ans;
int h1[N],cnt1,c;
int n,m,p,du[N];
int dfn[N],low[N];
int instack[N],v[N];
int st[N],top,f[N];
int num,scc,bel[N];
LL g[N],ansn;
queue<int> q;
void Add(int x,int y) {
	e[++cnt]=(Edge){y,h[x]};
	h[x]=cnt;
}
void Addt(int x,int y) {
	ee[++c]=(ArrayOfEdge){x,y};
}
void Add1(int x,int y) {
	e1[++cnt1]=(Edge){y,h1[x]};
	h1[x]=cnt1;
}
void Tarjan(int x) {
	dfn[x]=low[x]=++num;
	instack[x]=1;
	st[++top]=x;
	for (int i=h[x];i;i=e[i].nxt) {
		int y=e[i].to;
		if (!dfn[y]) {
			Tarjan(y);
			low[x]=min(low[x],low[y]);
		} else if (instack[y])
			low[x]=min(low[x],dfn[y]);
	}
	if (dfn[x]==low[x]) {
		int t;
		++scc;
		do {
			t=st[top--];
			bel[t]=scc;
			v[scc]++;//v爲該強連通分量的點數 
			instack[t]=0;
		} while (t!=x);
	}
}
bool cmp(ArrayOfEdge a,ArrayOfEdge b) {
	if (a.x!=b.x) return a.x<b.x;
	return a.y<b.y;
}
int main() {
	//freopen("semi.in","r",stdin);
	//freopen("semi.out","w",stdout);
	scanf("%d%d%d",&n,&m,&p);
	for (int i=1;i<=m;i++) {
		int u,v;
		scanf("%d%d",&u,&v);
		Add(u,v);
	}
	for (int i=1;i<=n;i++)
		if (!dfn[i]) Tarjan(i);
	for (int x=1;x<=n;x++) {
		for (int i=h[x];i;i=e[i].nxt) {
			int y=e[i].to;
			if (bel[x]==bel[y]) continue;
			Addt(bel[x],bel[y]);//記錄 
		}
	}
	sort(ee+1,ee+c+1,cmp);//排序 
	for (int i=1;i<=c;i++) {
		if (ee[i].x==ee[i-1].x&&ee[i].y==ee[i-1].y) continue;//去重 
		Add1(ee[i].x,ee[i].y);
		du[ee[i].y]++;
	}
	for (int i=1;i<=scc;i++)
		if (!du[i]) {
			q.push(i);
			f[i]=v[i];
			g[i]++;
		}
	while (!q.empty()) {
		int t=q.front();
		q.pop();
		ans=max(ans,f[t]);//找最長鏈 
		for (int i=h1[t];i;i=e1[i].nxt) {
			int y=e1[i].to;
			if (f[y]==f[t]+v[y]) {//相等時就累加方案數 
				g[y]=(g[y]+g[t])%p;
			} else if (f[y]<f[t]+v[y]) {//DP在DAG中求最長鏈,f[i]=max{f[j]+v[i]}(j->i有邊) 
				f[y]=f[t]+v[y];
				g[y]=g[t];//直接更新 
			}
			du[y]--;
			if (du[y]==0) q.push(y);
		}
	}
	printf("%d\n",ans);
	for (int i=1;i<=scc;i++) {
		if (f[i]==ans) {
			ansn=(ansn+g[i])%p;
		}
	}
	printf("%lld",ansn);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章