bzoj2438 杀人游戏 Tarjan强联通分量

       显然知道谁是杀手相当于知道所有人的身份。因此题目的答案即在无向图中选择最少的点,使得能遍历到至少n-1个点(最后一个点可以推理得到)。设结果为x,则答案为(n-x)/n。

        所以就可以用Tarjan找出强联通分量然后缩点,得到的DAG上入度为0的点即所要选择的点。如果存在某个点,这个点所在的强联通分量大小为1而且这个店所有的出边到达的点的入度都>1,那么这个点不选也可以遍历到n-1个点,x可以减一。

AC代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 100005
#define M 600005
using namespace std;

int n,m,tot,cnt,tp,dfsclk,fst[N],pnt[M],nxt[M],pos[N],low[N],stk[N],scc[N],sz[N];
int et[N],u[M],v[M]; bool vis[N];
int read(){
	int x=0; char ch=getchar();
	while (ch<'0' || ch>'9') ch=getchar();
	while (ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); }
	return x;
}
void add(int aa,int bb){
	pnt[++tot]=bb; nxt[tot]=fst[aa]; fst[aa]=tot;
}
void dfs(int x){
	pos[x]=low[x]=++dfsclk; stk[++tp]=x; int p;
	for (p=fst[x]; p; p=nxt[p]){
		int y=pnt[p];
		if (!pos[y]){ dfs(y); low[x]=min(low[x],low[y]); }
		else if (!scc[y]) low[x]=min(low[x],pos[y]);
	}
	if (low[x]==pos[x])
		for (cnt++; ; tp--){
			sz[cnt]++; scc[stk[tp]]=cnt;
			if (stk[tp]==x){ tp--; break; }
		}
}
bool jdg(int x){
	int p;
	for (p=fst[x]; p; p=nxt[p]) if (et[pnt[p]]==1) return 0;
	return 1;
}
int main(){
	n=read(); m=read(); int i,j,p,ans=0;
	if (n==1){ puts("1.000000"); return 0; }
	for (i=1; i<=m; i++){
		int x=read(),y=read(); add(x,y);
	}
	for (i=1; i<=n; i++) if (!pos[i]) dfs(i);
	m=0;
	for (i=1; i<=n; i++){
		for (p=fst[i]; p; p=nxt[p]){
			j=scc[pnt[p]];
			if (!vis[j] && scc[i]!=j){
				vis[j]=1;
				u[++m]=scc[i]; v[m]=j;
			}
		}
		for (p=fst[i]; p; p=nxt[p]) vis[scc[pnt[p]]]=0;
	}
	tot=0; for (i=1; i<=cnt; i++) fst[i]=0;
	for (i=1; i<=m; i++){ add(u[i],v[i]); et[v[i]]++; }
	for (i=1; i<=cnt; i++) if (!et[i]) ans++;
	for (i=1; i<=cnt; i++)
		if (!et[i] && sz[i]==1 && jdg(i)){
			ans--; break;
		}
	printf("%.6f\n",(double)(n-ans)/n);
	return 0;
}


by lych

2016.2.23

你可能感兴趣的:(DFS,Tarjan,强联通分量)