【洛谷P4819】杀人游戏
题目
题目链接:https://www.luogu.com.cn/problem/P4819
一位冷血的杀手潜入 Na-wiat,并假装成平民。警察希望能在 \(n\) 个人里面,查出谁是杀手。警察能够对每一个人进行查证,假如查证的对象是平民,他会告诉警察,他认识的人,谁是杀手,谁是平民。假如查证的对象是杀手,杀手将会把警察干掉。现在警察掌握了每一个人认识谁。每一个人都有可能是杀手,可看作他们是杀手的概率是相同的。
问:根据最优的情况,保证警察自身安全并知道谁是杀手的概率最大是多少。
思路
考虑到在一个强连通分量里,问任意一个人情况,只要这个人不是杀手,那么就可以把这个强连通分量里所有人的身份得知。所以果断缩点。
那么就得到了一张 DAG。我们只需要将这张 DAG 中所有入度为 0 的点(也就是原先的一个强连通分量)询问即可得出所有人的身份。设入度为 0 的点有 \(k\) 个,那么只需要问 \(k\) 次即可。
但是考虑一种情况:已经得知其中 \(n-1\) 个人均为平民,那么排除法即可知道最后一个人是杀手。所以如果存在一个大小为 1 且没有入度的强连通分量,那么只需要问 \(k-1\) 次即可。
那么答案就是 \(1-\frac{k}{n}\)。特殊情况 \(k\) 要减一。
时间复杂度 \(O(n)\)。
代码
#include <stack>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=300010;
int head[N],dfn[N],low[N],deg[N],pos[N],U[N],V[N];
int n,m,tot,cnt,ans;
bool vis[N];
vector<int> scc[N];
stack<int> st;
struct edge
{
int next,to;
}e[N];
void add(int from,int to)
{
e[++tot].to=to;
e[tot].next=head[from];
head[from]=tot;
}
void tarjan(int x)
{
dfn[x]=low[x]=++tot;
st.push(x);
vis[x]=1;
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (!dfn[v])
{
tarjan(v);
low[x]=min(low[x],low[v]);
}
else if (vis[v])
low[x]=min(low[x],low[v]);
}
if (low[x]==dfn[x])
{
int y;
cnt++;
do {
y=st.top(); st.pop();
vis[y]=0; pos[y]=cnt;
scc[cnt].push_back(y);
} while (y!=x);
}
}
int check()
{
for (int i=1;i<=n;i++)
if (!deg[i])
{
bool flag=1;
for (int j=head[i];~j;j=e[j].next)
if (deg[e[j].to]==1)
{
flag=0;
break;
}
if (flag) return 1;
}
return 0;
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++)
{
scanf("%d%d",&U[i],&V[i]);
add(U[i],V[i]);
}
tot=0;
for (int i=1;i<=n;i++)
if (!dfn[i]) tarjan(i);
for (int i=1;i<=m;i++)
if (pos[U[i]]!=pos[V[i]]) deg[pos[V[i]]]++;
for (int i=1;i<=cnt;i++)
if (!deg[i]) ans++;
memset(deg,0,sizeof(deg));
for (int i=1;i<=m;i++)
deg[V[i]]++;
printf("%0.6lf",1.0-1.0*(ans-check())/n);
return 0;
}