bzoj 2438: [中山市选2011]杀人游戏

算法:tarjan缩点

难度:NOIP   细节

题解:首先,易证,如果有环,只需查证其中的一个点即可,所以首先用tarjan缩点

           然后,必须查证的人的个数等于入度为0的强连通分量个数!

         但是,这样并不能AC,我们考虑下面这种情况:

       

感谢 GXZlegend 提供的图片

 这时只需要查证2/1即可,可以不查证1/2

当一个入度为0的点,其中元素为1,而它的出边所到的点的入度都>1,则ans--。 因为它们可以被别的点更新。

最后 (n-ans)/n ->即为存活概率。

时间复杂度O(n+m)

注意:m=0的时候需要特判!!! 

注意:如果一共只有3个人,已知2个人的信息,则第三个人可以不查证。

所以如果

input: 3 1 

           1   2 

output:0.666667 

所以,在找不需要查证的人的时候,如果他与任何人都没有连边也需要判断!

代码如下:

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <queue>
#define ll long long
#define N 100005
#define M 300005
using namespace std;
struct node
{
    int next;
    int to;
    int val;
}edge[M];
struct nod
{
    int next;
    int to;
    int val;
}edg[M];
int head[N],hea[N];
int dfn[N],low[N],my_stack[N];
int vis[N],posi[N];
int dep,cnt,top;
int scr_cnt,scr_num[N],inr[N];
int ctt,ctq;
void init()
{
    dep=0;
    cnt=0;
    top=0;
    scr_cnt=0;
    ctt=1;
    ctq=1;
    memset(hea,-1,sizeof(hea));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(head,-1,sizeof(head));
}
void add(int u,int v)
{
    edge[ctt].next=head[u];
    edge[ctt].to=v;
    head[u]=ctt++;
}
void add2(int u,int v)
{
    edg[ctq].next=hea[u];
    edg[ctq].to=v;
    hea[u]=ctq++;
}
void tarjan(int rt)
{
    dfn[rt]=low[rt]=++dep;
    my_stack[++top]=rt;
    for(int i = head[rt];i != -1;i=edge[i].next)
    {
        int to=edge[i].to;
        if(!dfn[to])
        {
            tarjan(to);
            low[rt]=min(low[to],low[rt]);
        }else if(!posi[to])
        {
            low[rt]=min(low[rt],dfn[to]/*刚刚写成了dfn[rt]*/);
        }
    }
    if(dfn[rt]==low[rt])
    {
        scr_cnt++;
        int v=-1;
        int t=0;
        while(v!=rt)
        {
            v=my_stack[top];
            top--;
            t++;
            posi[v]=scr_cnt;
        }
        scr_num[scr_cnt]=t;
    }
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    if(m==0) 
    {
    	printf("%.6lf\n",1.0/n);
    	exit(0);
    }/*else if(n==1)
    {
    	puts("1.000000");
    	exit(0);
    }*/
    init();
    for(int i = 1;i <= m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
    }
    for(int i = 1;i <= n;i++)
    {
        if(dfn[i]==0)
        {
            tarjan(i);
        }
    }
    for(int i = 1;i <= n;i++)
    {
    	for(int j = head[i];j != -1;j=edge[j].next)
        {
            int to=edge[j].to;
            if(posi[to]!=posi[i])
        	{
        		inr[posi[to]]++;
        		add2(posi[i],posi[to]);
        	}
        }    
    }
    int ans=0,fla=0;
    for(int i = 1;i <= scr_cnt;i++)
    {
    	if(!inr[i]) ans++;
    }
    if(ans!=1)
	{
		for(int i = 1;i <= scr_cnt;i++)
	    {
	    	int pp=0;
	    	if(scr_num[i]==1&&inr[i]==0)
	    	{
	  			int u=0;
	        	for(int j = hea[i];j != -1;j=edg[j].next)
	        	{
	        		u=1;
	        		int to=edg[j].to;
	        		if(inr[to]>1) pp=1;
	        		else
	        		{
	            		pp=0;
	            		break;
	            	}
	        	}
	        	if(pp||(u==0))
	        	{
	        		fla=1;
	        		break;
	        	}
	        }
	    }
    	if(fla) ans--;	
	} 
    double tmp=(1.0*(n-ans))/(n);
    printf("%.6lf",tmp);
    return 0 ;
} 
/*
6 10
1 2 
1 4 
2 5 
2 3 
3 1 
4 6 
4 3 
5 3 
5 4 
6 2 
*/

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值