1. 程式人生 > >HAOI2006 受歡迎的牛 (tarjan縮點)

HAOI2006 受歡迎的牛 (tarjan縮點)

題目大意就是要你求出這個圖中所有的能夠被其它點所連通的點的個數,我們考慮這裡面所有滿足這些性質的點,如果只有兩個點的情形,那麼必定存在這樣的路徑使得A到B並且B到A,對於後面每加進來的一個點也一定是在兩個點的路徑之中,我們就會發現對於一個連通圖來說,這些點必定會連成一個環,我們如果把這個點縮成一個點,那麼這個點的出度一定是為0的,假設它的出度不為0的話,那麼必定有一個點可以到達這個點,而這一個點本身又是被所有點到達的,那麼出去的那個點必定也應該在環裡面,而這個又是和它在環外是矛盾的,因此我們對這個圖進行一次tarjan縮點,縮點之後去找那個出度為0的那個點就可以了,但是如果這樣的點超過了一個,那就說明這個圖是不連通的,答案也就不存在了。 然後就是對tarjan縮點的一些理解,tarjan縮點是給每個點都加了一個時間陣列dfn,代表這個點進入的時候,然後用一個low陣列來記錄這個點所能到達的最早的時間,假如某個點的dfn與low最終相等的話,那麼就說明這個點就連成了一個環,這裡我們用一個棧去記錄下結點,然後這些結點會形成一條鏈,我們這個時候再把這些點一個一個染色,就可以判定哪些點是在一個環裡面了。 這裡用棧去儲存結點的時候要注意對於每一個點我們讓它入棧的時候都要打上標記判斷它是不是在棧裡面,退出棧的時候也要取消標記,然後我們找到那些已經進入過的點,我們只要在棧內的去更新它的low值就可以了。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e4+50;
const int maxm=5e4+50;
struct edge{ int v,next; }e[maxm<<2];
int cnt=0,tim=0,top=0,col=0,k,front[maxn<<2],dfn[maxn],low[maxn],s[maxn],degree[maxn],id[maxn],sum[maxn],instack[maxn]; 
void inline addedge(int u,int v){ e[++cnt].v=v; e[cnt].next=front[u]; front[u]=cnt; }
void tarjan(int u) {
	dfn[u]=low[u]=++tim;
	s[++top]=u,instack[u]=1;
	for(int i=front[u];i;i=e[i].next) {
		int v=e[i].v;
		if(!dfn[v]) {
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(instack[v]) low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]) {
		++col;
		do{
			k=s[top];top--;
			id[k]=col;
			sum[col]++; 
			instack[k]=0;
		}while(k!=u);
	}
}
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1,u,v;i<=m;i++) {
		scanf("%d%d",&u,&v);
		addedge(u,v);
	}
	for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);

	for(int u=1;u<=n;u++) {
		for(int i=front[u];i;i=e[i].next) {
			int v=e[i].v;
			if(id[v]!=id[u])
			degree[id[u]]++;
		}
	}
	
	int pos=-1;
	bool flag=false;
	for(int i=1;i<=col;i++) {
		if(!degree[i]) {
			if(flag) {printf("0\n");return 0;}
			pos=i;
			flag=true;	
		}		
	}
	printf("%d\n",sum[pos]);
	return 0;
}