题解——杀人游戏

图论建模-杀人游戏

杀人游戏

[中山市选]杀人游戏
题目描述一位冷血的杀手潜入Na-wiat,并假装成平民。警察希望能在\(N\)个人里面,查出谁是杀手。警察能够对每一个人进行查证,假如查证的对象是平民,他会告诉警察,他认识的人,谁是杀手,谁是平民。假如查证的对象是杀手,杀手将会把警察干掉。现在警察掌握了每一个人认识谁。每一个人都有可能是杀手,可看作他们是杀手的概率是相同的。
问:根据最优的情况,保证警察自身安全并知道谁是杀手的概率最大是多少?
输入格式
第一行有两个整数 \(N,M\)
接下来有 \(M\) 行,每行两个整数 \(x,y\),表示 \(x\) 认识 \(y\)\(y\) 不一定认识 \(x\) ,例如President同志) 。
注:原文zz敏感内容已替换
输出格式
仅包含一行一个实数,保留小数点后面 \(6\) 位,表示最大概率。
样例 #1

样例输入 #1

5 4 
1 2 
1 3 
1 4 
1 5

样例输出 #1

0.800000

提示

警察只需要查证\(1\)。假如\(1\)是杀手,警察就会被杀。假如\(1\)不是杀手,他会告诉警察\(2,3,4,5\)谁是杀手。而\(1\)是杀手的概率是\(0.2\),所以能知道谁是杀手但没被杀的概率是\(0.8\)

对于\(100\%\)的数据有\(1≤N≤100000,0≤M≤300000\)
题意简述:给定一张有向图上有\(n\)个点,每个点都有同等的概率为黑点,黑点只有一个,从一个节点出发可以知道所有可达点度数,求确定黑点的概率。

如何想到图论建模呢,事实上,当我们面对类似于这种单向的二元关系问题,并且需要借助关系确定某些信息时,便可以想图论建模的思路

那么梳理一下思路,如果我们将每个人认识的人进行连一条有向边,记作\((u,v)\),那么我们再来考虑这张图

很明显,如果若干个点处于同一个\(SCC\)之中,问哪一个元素都是一样的,这启发我们将图缩点,变成一个\(DAG\),因为是在最优情况下,于是我们可以思考如何用最少的次数确定每一个元素

引理

一般情况下,确定每一个元素的颜色,最少的操作次数一定为零入度点的个数,
先证必要性:

显然,零入度点不可能被其他点所确定,故至少需要零入度点个数的操作次数才能覆盖整张图

再证充分性:

一个点\(v\)被覆盖当且仅当至少存在一条边\((u,v)\)\(u\)被覆盖,那么我们对于每一个点类似递归思想一直追溯到不存在这样的边,此时这个最后追溯到的点确定之后\(v\)也就确定了,故数学归纳法易证

然后我们来思考有无特殊情况,考虑原图中的一个点\(p\)在怎样的情况下才可以不选择而被确定。若\(p\)点是一个孤立点,亦或者它所连的点的度数均大于等于2,此时这个点就可以先不急着需要它的颜色,那么我们可以将其放在最后解决,那么在所有满足要求的点\(p\)中,有一个点会因为其他所有点都被确定了而无需确定,这样就可以少选一次
\(QED.\)

结合引理,我们得到了本题的算法流程

  1. 建图,执行缩点
  2. 对于缩点后的\(DAG\),统计零入度点数量,记为\(c\),统计是零入度点,所在强连通分量大小为1并且满足其所连接的强连通分量的入度均大于1的\(SCC\)数量,记为\(p\)
  3. 最终答案为:\(ans=\frac{n-c+min(p,1)}{n}\)

对于本题需要注意的点是,在缩点建立新图的时候,很有可能出现重边的情况,大部分使用\(map\)进行优化,速度较慢,这里有一个更快的方式:

注意我们的\(Tarjan\)已经可以求出每一个\(SCC\)包含哪些点了,我们可以设一个大小为\(n\)的一维数组\(vis\),对于编号为\(i\in[1,cnt]\)的强连通分量进行考虑

  1. 清空\(vis\),这一步可以开一个\(vector\)记录上一个强连通分量所连接的强连通分量进行撤销,保证复杂度
  2. 遍历第\(i\)个强连通分量的元素\(u\),对\(u\)执行操作3
  3. 遍历\(u\)的所有出边,在同一个强连通分量的不管,不在同一个强连通分量的,设后继点为\(v\),则若\(vis[c[v]]\)未曾标记,将其入度加一并标记,若被标记,则不管

时间复杂度\(O(M)\)

本题总时间复杂度:\(O(M+N)=O(M)\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<stack>
using namespace std;
#define N 150000
#define M 650000
int head[N],n,m,ans,p,num,ver[M],nxt[M],c[N],siz[N],in[N],cnt[N],tot,scc_cnt,vis[N],dfn[N],low[N],s[N],inn[N];
int shead[N],sver[M],snxt[M],stot;
void add_s(int u,int v){
	snxt[++stot]=shead[u],sver[shead[u]=stot]=v;
}
vector<int>scc[N];
stack<int>t;
void add(int u,int v){
	nxt[++tot]=head[u],ver[head[u]=tot]=v;
}
void tarjan(int u){
	t.push(u);vis[u]=1;
	dfn[u]=low[u]=++num;
	for(int i=head[u];i;i=nxt[i]){
		int v=ver[i];
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(vis[v])low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){
		int v;
		scc_cnt++;
		do{
			v=t.top();t.pop();
			vis[v]=0;scc[scc_cnt].push_back(v);
			c[v]=scc_cnt;siz[scc_cnt]++;
		}while(u!=v);
	}
}
bool check(int a){
	for(int i=0;i<scc[a].size();i++){
		int u=scc[a][i];
		for(int i=head[u];i;i=nxt[i]){
			int v=ver[i];
			if(c[u]==c[v])continue;
			if(in[c[v]]<2)return true;
		}
	}
	return false;
}
void solve(){
	if(n==1){
		printf("1.000000\n");
		return ;
	};
	for(int i=1;i<=scc_cnt;i++){
		int num=0;
		cnt[i]=1;
		for(int j=0;j<siz[i];j++){
			int u=scc[i][j];
			for(int k=head[u];k;k=nxt[k]){
				if(cnt[c[ver[k]]])continue;
				in[c[ver[k]]]++;
				cnt[c[ver[k]]]=1;
				s[++num]=c[ver[k]];
			}
		} 
		cnt[i]=0;
		while(num)cnt[s[num--]]=0;
	}
	int ans1=0,flag=1;
	for(int i=1;i<=scc_cnt;i++){
		if(in[i]==0){
			ans1++;
		}
	}
	if(ans1==1){
		printf("%.6f\n",1.0-1.0/n);
		return ;
	}
	for(int i=1;i<=scc_cnt;i++)if(in[i]==0&&!check(i)){ans1--;break;}
	printf("%.6f\n",1.0*(n-ans1)/n);
	return ;
}
void init(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
		inn[v]++;
	}
	for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);
	solve();
}
int main(){
	init();
}
posted @ 2022-11-30 22:46  spdarkle  阅读(105)  评论(0编辑  收藏  举报