P4819 [中山市选] 杀人游戏
题目大意
详细题目传送门
给出一个有向图,点有黑白颜色,且只有一个黑点。如果选择一个点 \(u\) 可以知道相邻的点的所有颜色。求在不选择黑点后能知道黑点在哪个点上的概率 \(p\)。
\(n\leq 10^5,m\leq 3\cdot 10^5\)
思路
首先发现对于一个强联通分量,只要不访问到黑点那么就可以知道这个强联通分量里面的所有点的颜色。所以就先缩点,转成一个 DAG。在转成 DAG 后发现访问所有入度为 \(0\) 的点就可以知道原图所有点的颜色。那么概率就是 \(1-\frac{c}{n}\),其中 \(c\) 表示为入度为 \(0\) 的强联通分量数量。
感觉就做完了,可是交上去发现是 \(30\) 分。发现不一定要访问所有入度为 \(0\) 的点,如果假设不访问一些入度为 \(0\) 的强联通也可以知道整张图的颜色的话那就没必要选。然后发现这个不选的强联通分量包含的个数大于 \(1\) 的话就一定不行,因为入度是 \(0\) 所以里面的点只能由这个强联通分量里面的点自己访问。那么如果这个强联通分量所连接的其他强联通分量只能由它转移也不行,即出边的强联通分量入度为 \(1\)。然后发现这个不选的强联通分量只能存在一个,因为如果有 \(2\) 个不选的话则这两个之间也是无法确定有没有凶手的,所以无法判断。
总结一下,就是在缩点后,求出 \(c\) 来判断是否存在一个入度为 \(1\) 的强联通分量满足:
- 大小为 \(1\)。
- 没有出边的强联通分量度数大于 \(1\)
如果存在那么答案变为 \(1-\frac{c-1}{n}\)。
代码
#include<bits/stdc++.h>
#define rep(i,a,b) for(register ll i=(a);i<=(b);++i)
#define endl '\n'
using namespace std;
typedef long long ll;
const ll MAXN=1e5+5;
const ll MAXM=3e5+5;
ll n,m;
vector<ll>adj[MAXN];
ll pre[MAXN],low[MAXN],scc[MAXN],ts,sci;
stack<ll>ins;
void dfs(ll u){
pre[u]=low[u]=++ts;
ins.push(u);
for(auto v:adj[u]){
if(!pre[v]){
dfs(v);
low[u]=min(low[u],low[v]);
}else if(!scc[v]){
low[u]=min(low[u],pre[v]);
}
}
if(pre[u]==low[u]){
++sci;
while(true){
ll tp=ins.top();
ins.pop();
scc[tp]=sci;
if(tp==u){
break;
}
}
}
}
vector<ll>g[MAXN];
set<pair<ll,ll>>se;
ll ind[MAXN],sz[MAXN];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m;
rep(i,1,m){
ll u,v;
cin>>u>>v;
adj[u].push_back(v);
}
rep(i,1,n){
if(!pre[i]){
dfs(i);
}
}
rep(i,1,n){
sz[scc[i]]++;
}
rep(u,1,n){
for(auto v:adj[u]){
if(scc[v]!=scc[u]&&!se.count({scc[u],scc[v]})){
ind[scc[v]]++;
se.insert({scc[u],scc[v]});
g[scc[u]].push_back(scc[v]);
}
}
}
ll val=0,pc=0;
rep(i,1,sci){
if(ind[i]==0){
val++;
if(!pc&&sz[i]==1){
bool gd=true;
for(auto v:g[i]){
if(ind[v]==1){
gd=false;
break;
}
}
if(gd){
pc=1;
}
}
}
}
printf("%0.6lf\n",1-1.0*((val-pc)*1.0/n));
return 0;
}