BZOJ 2438 杀人游戏 强联通分量tarjan缩点

题意: 网址.

方法: 强联通分量tarjan缩点.

解析: 读完题后画一下样例 (这样例并没有什么用),所以我们还是草拟一组样例来做吧,于是找到那个 Victoria的舞会   3  的样例来看



 
  先不管这个图输出什么,首先来分析查询每个人时候的状态。
 每一个人是杀手的概率是 1 / n(这是毋庸置疑的
 {1,2,3} 是一个强连通分量,如果我们从1开始查询,那么他是杀手的概率是1/n,但是只要知道了1,我们就可以 知道2,3是不是杀手,所以把1,2,3看在一起,知道1,就知道2,3,所以这一个强连通分量中有杀手的概率就是 1/n。于是喜闻乐见地进一步分析,发现这时 每一个强连通分量是杀手的概率是1/n,那么答案就应该是1-3/n 应 该是 4/7 (加法原理)
主旨: 缩点后重新建图,然后对于ans个强连通分量做查询;
注意: 如果在查询了n-1个点之后,还没有找到杀手,则第n个点一定是杀手,所以此时的概率就应该 增加 1/n ,即最后一 个点不用搜索。

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std ;
struct node
{
    int to ;
    int next ;
    int from ;
};
node edge[300001] ;
node edge2[300001] ;
int head[100001] ;
int z[100001] ;
int inz[100001] ;
int low[100001] ;
int deep[100001] ;
int belong[100001] ;
int num[100001] ;
int in[100001] ;
int head2[100001] ;
int top , tot , cnt , ans , cnt2;
void init()
{
    memset(head , -1 , sizeof(head)) ;
    memset(head2 , -1 , sizeof(head2)) ;
    cnt2 = 1 ;
    cnt = 1 ;
}
void edgeadd(int from , int to)
{
    edge[cnt].from = from ;
    edge[cnt].to = to ;
    edge[cnt].next = head[from] ;
    head[from] = cnt ++ ;
}
void edgeadd2(int from , int to)
{
    edge2[cnt2].from = from ;
    edge2[cnt2].to = to ;
    edge2[cnt2].next = head2[from] ;
    head2[from] = cnt2 ++ ;
}
void tarjan(int x)
{
    deep[x] = low[x] = ++tot ;
    inz[x] = 1 , z[++top] = x ;
    for(int i = head[x] ; i != -1 ; i = edge[i].next)
    {
        int to = edge[i].to ;
        if(!deep[to])
        {
            tarjan(to) ;
            low[x] = min(low[x] , low[to]) ;
        }else if(inz[to]) low[x] = min(low[x] , deep[to]) ;
    }
    if(low[x] == deep[x])
    {
        ans ++ ;
        int t ;
        do
        {
            t = z[top--] ;
            inz[t] = 0 ;
            belong[t] = ans ;
            num[ans] ++ ;
        }while(t!=x) ;
    }
}
int jud(int i)
{
    for(int j = head2[i] ; j != -1 ; j = edge2[j].next)
    {
        int to = edge2[j].to ;
        if(in[to] == 1) return 0 ;
    }
    return 1 ;
}
int main()
{
    int n , m ;
    init() ;
    scanf("%d%d" , &n , &m) ;
    for(int i = 1 ; i <= m ; i++)
    {
        int x , y ; 
        scanf("%d%d" , &x , &y) ;
        edgeadd(x , y) ;
    }
    if(n == 1)
    {
         printf("1.000000\n") ; 
         return 0 ;
    }
    for(int i = 1 ; i <= n ; i++)
    {
        if(!deep[i])
        {
            tarjan(i) ;
        }
    }
    for(int i = 1 ; i <= cnt ; i++)
    {
        int x = edge[i].from , y = edge[i].to ;
        if(belong[x] != belong[y])
        {
            in[belong[y]] ++ ;
            edgeadd2(belong[x] , belong[y]) ;
        }
    }
    int tmp = 0 ;
    for(int i = 1 ; i <= ans ; i++)
    {
        if(in[i] == 0) tmp ++ ;
    }
    int flag = 0 ;
    for(int i = 1 ; i <= ans ; i++)
    {
        if(tmp > 1 && in[i]==0 && num[i] == 1)
        {
            if(jud(i))
            {
                tmp --  ;
                break ;
            }
        }
    }
    printf("%.6lf\n" , (double)(n-tmp)/n) ;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值