[BZOJ 2438] 中山市选2011 杀人游戏 · Tarjan

一开始想到用并查集直接求联通,但是这样貌似并不可行。。。

每调查一个人,都会增加1/n的被杀概率,所以要尽量少调查人。

如果我们调查了一个人,那么和他认识的人我们全都能知道,以此类推,向下的人都可以知道,所以我们只要求入度为0的点的个数,这些人我们只能通过直接询问他才能知道是不是杀手,另外的人都可以通过这些人知道。

对于一个环里的人,我们只要知道一个点就能摸清所有点,所以先Tarjan缩环。

另外这题还有一个特别精巧的地方,这n个点构成了scc个分量,有一个分量只包含一个点,如果这个分量没有出边,或者他连向的点都可以通过其他分量到达,那么我们只要查其他scc-1个点就可以摸清n-1个点,最后一个点自然也清楚了,这时候要不算这个点的询问次数。(只有可能存在一个点,因为知道n-2个点并不能摸清剩下2个点)

#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <iostream>
using namespace std;

const int N=300005;
struct arr{
	int node,nxt;
}e1[N],e2[N];
int head1[N],head2[N],tot1,tot2;
int n,m,sumin[N],sumout[N],ans,num[N];

void add1(int x,int y){
	e1[++tot1].node=y;
	e1[tot1].nxt=head1[x];
	head1[x]=tot1;
}
void add2(int x,int y){
	e2[++tot2].node=y;
	e2[tot2].nxt=head2[x];
	head2[x]=tot2;
	sumin[y]++;sumout[x]++;
}

int dfn[N],low[N],tim,stack[N],top,scc,belong[N];
bool check[N];

void tarjan(int t){
	dfn[t]=low[t]=++tim;
	check[t]=1; stack[++top]=t;
	int u,v;
	for (int i=head1[t];i;i=e1[i].nxt){
		int x=e1[i].node;
		if (!dfn[x]) {
			tarjan(x);
			low[t]=min(low[t],low[x]);
		}
		else 
			if (check[x]) low[t]=min(low[t],dfn[x]);
	}
	if (dfn[t]==low[t]){
		int now=0;scc++;
		while (now!=t){
			now=stack[top--];
			belong[now]=scc;
			check[now]=0;
			num[scc]++;
		}
	}
}

void rebuild(){
	bool vis[N];
	memset(vis,0,sizeof vis);	//防止加重边 
	for (int i=1;i<=n;i++){
		for (int j=head1[i];j;j=e1[j].nxt){
			int u=belong[i],v=belong[e1[j].node];
			if (u!=v && !vis[v]) {vis[v]=1;add2(u,v);}
		}
		for (int j=head1[i];j;j=e1[j].nxt){
			int u=belong[i],v=belong[e1[j].node];
			if (u!=v) vis[v]=0;
		}
	}
}

bool judge(int i){
	if (sumin[i] || num[i]!=1) return 0;	//入度不为0或者联通块中不止一个点则跳出
	for (int j=head2[i];j;j=e2[j].nxt)
		if (sumin[e2[j].node]==1) return 0;
	return 1;
}

int main(){
	cin>>n>>m;
	for (int i=1;i<=m;i++) {
		int x,y;cin>>x>>y;
		add1(x,y);
	}
	for (int i=1;i<=n;i++)
		if (!dfn[i]) tarjan(i);
	rebuild();
	for (int i=1;i<=scc;i++)
		if (!sumin[i]) ans++;
	for (int i=1;i<=scc;i++)
		if (judge(i)) {
			ans--;break;
		}
	printf("%.6lf\n",(n-ans)*1.0/n);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值