1. 程式人生 > 實用技巧 >圖論之殺人遊戲

圖論之殺人遊戲

題目

思路

對於一張圖來說,我們將其分為鏈(包括帶環鏈)和環

  • 對於鏈,從鏈頂(入度為0)開始dfs記錄鏈的個數及大小,注意,大小為1的單點也包括在其中了;
  • 處理完鏈後,對於單個的環來說,所有的點的入度都不為0,所以在處理完鏈之後還沒有處理的就是環了,再dfs一下就ok了;
    顯然,對於每一個鏈狀結構(或者環狀結構)只需要減去一次鏈首(或環中任意一個點)是殺手的概率就可以了。
    接下來來考慮單點情況
  • 對於單點來說,如果將其他的點都查詢完了,最後剩下一個單點,那麼這個點是不需要查詢的;
  • 但是在上面第一種情況下,每一個單點都被考慮了一次,實際對於n個單點,我們只需要考慮n-1次,所以對於存在單點的圖,我們把最後一個單點的情況加回來;
    結束了???然而並沒有

對於上面情況,如果我們正向遍歷和反向遍歷結果是不一樣的

  • 正向,圖中沒有單點,一條鏈和一個帶環鏈
  • 反向,圖中有一個單點,一個帶環鏈,

所以我們需要正向反向都遍歷一遍,取最後概率最大值

程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn=1000000+10;
const int maxm=300000;
int head[maxn],ver[maxm],Next[maxm],tot,cnt;
void add(int x,int y){
	ver[++tot]=y,Next[tot]=head[x],head[x]=tot;
}
int siz[maxn];
int rd[maxn];
bool vis[maxn];
int n,m;
void dfs(int x){//dfs求每個鏈狀結構或者環狀結構的大小
	vis[x]=1;
	siz[cnt]++;
	for(int i=head[x];i;i=Next[i]){
		if(!vis[ver[i]])dfs(ver[i]);
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1,x,y;i<=m;i++){
		scanf("%d%d",&x,&y);
		add(x,y);
		rd[y]++;//記錄入度
	}
	int one=0;//記錄單點個數
	double ans=1;
	for(int i=1;i<=n;i++){//正向遍歷處理鏈
		if(!vis[i]&&rd[i]==0){
			cnt++;
			dfs(i);
			ans-=(1.0/n);//每增加一個鏈狀結構,就相應減去鏈首為殺手的概率
			if(siz[cnt]==1)one++;
		}
	}
	for(int i=1;i<=n;i++){//正向遍歷處理環
		if(!vis[i]){
			cnt++;
			dfs(i);
			ans-=(1.0/n);
		}
	}
	if(one)ans+=(1.0/n);//如果存在單點,把多減去的加上
	/*====================接下來反向遍歷=========================*/
	memset(siz,0,sizeof(siz));
	memset(vis,0,sizeof(vis));
	int one2=0;double ans2=1;
	for(int i=n;i>=1;i--){
		if(!vis[i]&&rd[i]==0){
			cnt++;
			dfs(i);
			ans2-=(1.0/n);
			if(siz[cnt]==1)one++;
		}
	}
	for(int i=n;i>=1;i--){
		if(!vis[i]){
			cnt++;
			dfs(i);
			ans2-=(1.0/n);
		}
	}
	if(one2)ans2+=(1.0/n);
	printf("%.6lf\n",max(ans,ans2));//取最大值
}