[BZOJ2438] 中山市选2011 杀人游戏 Tarjan

[BZOJ2438] 中山市选2011 杀人游戏 Tarjan

题目链接:中山市选 杀人游戏


​ 很明显的图论,根据题意建图,如果 \(x\) 认识 \(y\),那么连一条由 \(x\) 指向 \(y\)单向边

​ 注意到一个性质:如果一个人的身份得知,他所认识的人的身份也都得知。

​ 那么在同一个联通分量中的人只要知道一个人的身份就好了,我们考虑使用 Tarjan 算法缩点。缩点完之后的图构成 DAG。由于我们的信息具有传递性。也就是说,一个人的身份知道了,他认识的人的身份都知道了,同样的,他所认识的人所认识的人的身份也都知道了。所以,在 DAG 中,只要入度为 \(0\) 的强连通分量中有一个人的身份知道了,所有人的身份也就都知道了。这样我们就可以抓住凶手。所以我们需要统计入度为 \(0\) 的连通分量个数,假设它的值为 \(c\)。由于随机选一个人,这个人是凶手的概率是 \(\dfrac{1}{n}\),选 \(c\) 个人(每个连通分量里的人选一个), 有一个人是凶手的概率就是 \(\dfrac{c}{n}\)。答案就是 \(1-\dfrac{c}{n}\)

​ 但是上面我们陷入了一个误区,我们一定要把所有人的身份都查出来才能确定凶手呢。我们是不是忘了一个叫排除法的东西。如果我们确定了 \(n-1\) 个人的身份,最后那个人的身份是不是也就知道了?所以我们要看看入度为 \(0\) 的连通分量中,有没有满足不用查这个连通分量也能得知 \(n-1\) 个人的身份这个性质的连通分量。由于入度为 \(0\) 的连通分量中人的身份只能靠同一个连通分量的来得知,如果不用查这个联通分量也能得知 \(n-1\) 个人,那么这个连通分量的大小只能是 \(1\)。同时,这个连通分量所连的连通分量必须被其他连通分量相连,即入度不为 \(1\)。这样即使不调查这个大小为 \(1\) 的连通分量,它所连的连通分量的人的身份也可以由其他连通分量推来。那么,如果存在这么一个连通分量,答案就变成了 \(1-\dfrac{c-1}{n}\)。值得注意的是:如果有两个或者更多这样的连通分量,答案还是 \(1-\dfrac{c-1}{n}\)。因为我们并不能使用排除法推出超过 \(1\) 个未知信息。

​ 所以统计答案的代码如下:

	int sum=0;
	for(int i=1;i<=num;++i)
		if(!in[i]) sum++;
	for(int i=1;i<=num;++i)
	{
		if(siz[i]!=1||in[i]) continue;
		int p=vec[i][0];bool flag=0;
		for(int j=0;j<e[p].size();++j)
		{
			int to=e[p][j];
			if(in[color[to]]==1)
				flag=1;
		}
		if(!flag)
		{
			sum--;
			break;
		}
	}
	double ans=1.0-1.0*sum/n;
	printf("%.6lf\n",ans);

​ 但是这题在统计连通分量入度时有个细节,我们知道,一个连通分量可能有好几条边连向同一个连通分量,这会导致连通分量的入度统计偏大,所以我们要使用 map 去重,代码如下:

	for(int i=1;i<=n;++i)
		for(int j=0;j<e[i].size();++j)
		{
			if(color[e[i][j]]!=color[i]&&!mp.count(make_pair(color[e[i][j]],color[i])))
			{
				in[color[e[i][j]]]++;
				mp[make_pair(color[e[i][j]],color[i])]=1;
			}
		}

​ 总时间复杂度就是 \(O(n+m\times\log(m))\)

全部代码如下:

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5+5;
int n,m,dfn[MAXN],low[MAXN],totdfn,in[MAXN],siz[MAXN],color[MAXN],num,out[MAXN];
bool flag[MAXN];
stack <int> st;
vector <int> e[MAXN],vec[MAXN];
map <pair<int,int>,bool> mp;
void dfs(int p)
{
	dfn[p]=low[p]=++totdfn;st.push(p);
	for(int i=0;i<e[p].size();++i)
	{
		int to=e[p][i];
		if(!dfn[to])
		{
			dfs(to);
			low[p]=min(low[to],low[p]);
		}
		else if(!flag[to]) low[p]=min(low[p],dfn[to]);
	}
	if(low[p]==dfn[p])
	{
		color[p]=++num;
		flag[p]=1;++siz[num];
		vec[num].push_back(p);
		while(st.top()!=p)
		{
			color[st.top()]=num;
			flag[st.top()]=1;
			vec[num].push_back(st.top());
			++siz[num];
			st.pop();
		}
		st.pop();
	}
}
int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;++i)
	{
		int u,v;
		scanf("%d %d",&u,&v);
		e[u].push_back(v);
	}
	for(int i=1;i<=n;++i)
		if(!dfn[i]) dfs(i);
	for(int i=1;i<=n;++i)
		for(int j=0;j<e[i].size();++j)
		{
			if(color[e[i][j]]!=color[i]&&!mp.count(make_pair(color[e[i][j]],color[i])))
			{
				in[color[e[i][j]]]++;
				mp[make_pair(color[e[i][j]],color[i])]=1;
			}
		}
	int sum=0;
	for(int i=1;i<=num;++i)
		if(!in[i]) sum++;
	for(int i=1;i<=num;++i)
	{
		if(siz[i]!=1||in[i]) continue;
		int p=vec[i][0];bool flag=0;
		for(int j=0;j<e[p].size();++j)
		{
			int to=e[p][j];
			if(in[color[to]]==1)
				flag=1;
		}
		if(!flag)
		{
			sum--;
			break;
		}
	}
	double ans=1.0-1.0*sum/n;
	printf("%.6lf\n",ans);
	return 0;
}

总结:Tarjan 细节题。

posted @ 2021-08-26 15:45  夜空之星  阅读(83)  评论(0编辑  收藏  举报