luogu P4819 [中山市选] 杀人游戏 题解 【强连通分量+缩点】

发布时间 2023-09-27 16:02:37作者: adolf_stalin

题目链接

P4819

思路分析

首先考虑这道题的连通性。容易发现这种类型的题目会容易产生环形的状态转移。假设我们知道了其中的一个点是否是黑白点,那么我们就可以知道所有点是否是黑白点。容易陷入一个误区:我们只能通过一个点知道他所相邻的最直接的点,如何确定相邻的点的状态?注意本题求的是存活且找到黑点的概率而不是期望一类,我们只需要能在黑点前停下就可以了。注意 tarjan强连通分量的一个重要性质:避免重边!虽然我之前做的题没有刻意卡这个性质。

代码

#include<iostream>
#include<cstdio>
#include<vector>
#include<iomanip>
using namespace std ;
const int N = 1e5+10 ;
const int M = 3e5+10 ;
int n ,m ;
vector<int> edge[N] ;
int low[N] ,dfn[N] ,tim ,sta[N] ,top ,col[N] ,cnt ;
bool vis[N] ;
inline void tarjan(int u){
    sta[++top] = u ;
    vis[u] = 1 ;
    low[u] = dfn[u] = ++tim ;
    for(int v : edge[u]){
        if(!dfn[v]){
            tarjan(v) ;
            low[u] = min(low[u] , low[v]) ;
        }else if(vis[v])    low[u] = min(low[u] , dfn[v]) ;
    }
    if(low[u] == dfn[u]){
        col[u] = ++cnt ;
        while(sta[top] != u)    col[sta[top]] = cnt ,vis[sta[top--]] = 0 ;
        vis[u] = 0 ,top-- ;
    }
}
int in[N] ,siz[N] ,tot ;
vector<int> g[N] ;
bool vise[M] ;
int main(){
    ios::sync_with_stdio(0) ,cin.tie(0) ,cout.tie(0) ;
    cin >> n >> m ;
    for(int i = 1,u,v;i <= m;++i){cin >> u >> v ;edge[u].push_back(v) ;}
    for(int i = 1;i <= n;++i)    if(!dfn[i]) tarjan(i) ;
    for(int i = 1;i <= n;++i)   siz[col[i]]++ ;
    for(int i = 1;i <= n;++i){
        for(int j : edge[i]){
            if(col[i] == col[j] || vise[col[j]]) continue ;
            g[col[i]].push_back(col[j]) ;
            vise[col[j]] = 1 ;in[col[j]]++ ;
        }
        for(int j : edge[i])    vise[col[j]] = 0 ;
    }
    bool flag = 1 ;
    for(int i = 1;i <= cnt;++i){
        if(!in[i])  tot++ ;
        if(in[i] || siz[i] > 1) continue ;
        if(flag == 0)   continue ;
        bool pp = 1 ;
        for(int j : g[i])
            if(in[j] <= 1){
                pp = 0 ;
                break ;
            }
        if(pp) tot-- ,flag = 0 ;
    }
    cout << fixed << setprecision(6) << 1.0 * (n - tot) / n ;
    return 0 ;
}