[强连通分量] BZOJ 2438 [中山市选2011]杀人游戏

本文介绍了一种解决特定概率问题的方法,通过图论中的缩点技巧将有向图简化为树形结构,进而求解未知节点的安全概率。讨论了使用Tarjan算法进行缩点的过程,并考虑了特殊情况下的优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意...看了题解才知道 认识关系是可以传递的啊

注意一个坑人的特判


一开始看到这个题还以为是关于概率和期望的问题,,结果发现这题的概率就是个没有技术含量的东西,,假如警察一共只询问了x个人的话,那么安全的概率就是1-x/n,,所以说关键就是求出x的值; 

我们发现如果将所有关系看作一棵树的话,那么我们只要调查树的根节点就可以了,也就是说我们的答案就是1-根节点数/节点总数;但是我们发现这个有向图并不是一棵树,而是有环存在的,这样我们就无法将图转换成一棵树,,这时我们可以考虑缩点,,因为如果关系构成环的话,那么我们调查环内的任何一个节点都可以将整个环调查出来,也就是说对于整个环,我们只需算作一次调查,所以我们可以将环用Tarjan算法缩成一个点,这样整个图就变成了一个有向无环图,即一棵树,然后我们将图遍历一遍,求出入度为1的节点(即根节点)个数,然后算出答案即可。 
还需要注意一个问题,由于题目的关系,我们可以知道如果最后只剩一个点没有调查,那么可以直接确定这个点的身份,就不用再次调查一遍了。那么这个单点应该怎么确定呢?首先它的入度要为0,其次它的出边所连的点应该可以包含在其他子树中(也就是相连点的入度大于1),那么这个点就可以放到最后,先询问其他点,这样最后这个点的身份也就确定了。所以如果我们发现有这样的点的话,令根节点数减1就可以了。


#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#define cl(x) memset(x,0,sizeof(x))
using namespace std;

inline char nc()
{
	static char buf[100000],*p1=buf,*p2=buf;
	if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
	return *p1++;
}

inline void read(int &x)
{
	char c=nc(),b=1;
	for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
	for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}

const int N=100005;
const int M=300005;

struct Stack{
	int s[N],p;
	void clear(){ p=0; cl(s); }
	void push(int x){ s[++p]=x; }
	void pop(){ s[p--]=0; }
	int top(){ return s[p]; }
	bool empty(){ return p==0; }
}S;

struct edge{
	int u,v; int next;
};

edge G[M];
int head[N],inum;

inline void add(int u,int v,int p)
{
	G[p].u=u; G[p].v=v; G[p].next=head[u]; head[u]=p;
}

int cnt,tot[N];
int clk,pre[N],low[N],scc[N];

#define V G[p].v

inline void Tarjan(int u)
{
	low[u]=pre[u]=++clk;
	S.push(u);
	for (int p=head[u];p;p=G[p].next)
		if (!pre[V])
			Tarjan(V),low[u]=min(low[u],low[V]);
		else if (!scc[V])
			low[u]=min(low[u],pre[V]);
	if (low[u]==pre[u])
	{
		++cnt;
		while (!S.empty())
		{
			int x=S.top(); S.pop();
			scc[x]=cnt; tot[cnt]++;
			if (x==u) break; 
		}
	}
}

int vst[N],deg[N];

namespace Graph{
	struct edge{
		int u,v; int next;
	}G[M];
	int head[N],inum;
	inline void add(int u,int v){
		int p=++inum; G[p].u=u; G[p].v=v; G[p].next=head[u]; head[u]=p;
	}
	inline bool Jud(int u){
		for (int p=head[u];p;p=G[p].next)
			if (deg[V]==1)
				return 0;
		return 1;
	}
}

int n,m,ans;

int main()
{
	int iu,iv;
	freopen("t.in","r",stdin);
	freopen("t.out","w",stdout);
	read(n); read(m);
	for (int i=1;i<=m;i++)
		read(iu),read(iv),add(iu,iv,++inum);
	for (int i=1;i<=n;i++)
		if (!pre[i])
			Tarjan(i);
	for (int i=1;i<=n;i++)
	{
		int u=i;
		for (int p=head[u];p;p=G[p].next)
			if (scc[u]!=scc[V] && !vst[scc[V]])
				vst[scc[V]]=1,Graph::add(scc[u],scc[V]),deg[scc[V]]++;
		for (int p=head[u];p;p=G[p].next)
			if (scc[u]!=scc[V])
				vst[scc[V]]=0;
	}
	for (int i=1;i<=cnt;i++)
		if (!deg[i])
			ans++;
	for (int i=1;i<=cnt;i++)
		if (!deg[i] && tot[i]==1 && Graph::Jud(i))
		{
			ans--; break;
		}
	printf("%.6lf\n",1.0-(double)ans/n);
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值