[JZOJ5522]. 图

题目描述

这个世界是一个有向图,图中有n个点m条边且无重边无自环,每秒第i条边出现的概率是p[i]/100,一开始Samjia在1点,每一秒假设Samjia在点x上,那么Samjia要从存在的边中选一条来走,不可以不走,如果不存在可以走的边,那么Samjia就会掉出这个世界,假设Samjia绝顶聪明,问最后Samjia可以成功到达n的概率是多少。精度是1e-6
对于40%的数据,2<=n<=8
对于70%的数据,2<=n<=20
对于100%的数据,2<=n<=50,0<=m<=n*(n-1),0<=p[i]<=100

题目分析

这道题看起来挺麻烦的,一开始看到有环就觉得很麻烦。
不妨设P[x]表示在x点到点n的概率,如果我们知道了它所有出边(x,y)的P[y],我们要怎么采取最优策略呢?我们来考虑一下所有情况,假设一种情况出现了若干条边,连向a[1..k],那么我们肯定是走P[a[i]]最大的一个a[i],不然就不优了。那么我们可以把后继按P从大到小排序,每次计算出P比他大的后继的边都不出现,而他出现的概率,就可以算出P[x]了。
问题是有可能有环,有些后继的P是无法确定的。
这个时候眼前摆了两条路。

逼近精度

你设个步数,比如10000。然后超过这个步数的路径长度概率都视为0。具体的,设f[i][j]表示走了i长度,走到j,它到n的概率。那么能够水一些分。事实证明,这场比赛worldwide_D这样写过了。

调整法

有个暴力,就是枚举所有P的相对大小的情况,然后取P[1]最大的那种作为答案。
你随机定一个顺序,然后按这个顺序能够确定P之间的关系,然后高斯消元。
由于你的顺序可能是错误的,你的P[1]可能会算出来比原来小,其他P可能也不一样。
这个时候,算出来的情况会和你定的顺序不一样,你可以取这个顺序作为新的顺序再继续做。
就是一个迭代过程,不断调整,直到一样,那么这是正确的答案了。
具体证明需要用到比较高深的知识,感受一下就好~反正顺序相同很显然是答案正确的必要条件,至于为什么这样做事对的就…以后会知道的~
不断调整,一个x的相对排名最多会变n次,那么最坏调整复杂度O(n^2),高斯消元O(n^3),总复杂度O(n^5)。实际上很难调整这么多次。

代码

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
using namespace std;
typedef long long ll;
typedef double db;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
#define cmax(a,b) (a=(a>b)?a:b)
#define cmin(a,b) (a=(a<b)?a:b)
const int N=2500+5,mo=1e9+7,M=55;
const db eps=1e-6;
db a[M][M],E[M],xs;
int rk[M],rrk[M],sa[M],mx,i,j,k,n,m,x,p,y,z,pp;
int tt,b[N],nxt[N],fst[N],c[N],d[N];
void cr(int x,int y,int z)
{
    tt++;
    b[tt]=y;
    c[tt]=z;
    nxt[tt]=fst[x];
    fst[x]=tt;
}
bool cmp(int x,int y)
{
    return rk[b[x]]<rk[b[y]];
}
bool cmp2(int x,int y)
{
    return E[x]>E[y];
}
void geteq()
{
    fo(i,1,n) fo(j,1,n+1) a[i][j]=0;
    fo(x,1,n-1)
    {
        d[0]=0;
        for(p=fst[x];p;p=nxt[p])
            d[++d[0]]=p;
        sort(d+1,d+1+d[0],cmp);
        a[x][x]=-1;
        xs=1;
        fo(i,1,d[0])
        {
            p=d[i];
            a[x][b[p]]=xs*db(c[p])/100.0;
            xs*=1.0-db(c[p])/100.0;
        }
    }
    a[n][n]=1;
    a[n][n+1]=1;
}
void gauss()
{
    fo(i,1,n)
    {
        mx=i;
        fo(j,i+1,n) if (fabs(a[j][i])>fabs(a[mx][i])) mx=j;
        fo(j,1,n+1) swap(a[i][j],a[mx][j]);
        if (fabs(a[i][i])<eps) continue;
        fo(j,i+1,n)
        {
            xs=a[j][i]/a[i][i];
            fo(k,i,n+1) a[j][k]-=a[i][k]*xs;
        }
    }
    fd(i,n,1)
    {
        if (fabs(a[i][i])<eps) continue;
        fd(j,i-1,1)
        {
            xs=a[j][i]/a[i][i];
            fo(k,j,n+1) a[j][k]-=a[i][k]*xs;
        }
    }
    fo(i,1,n)
        E[i]=a[i][n+1]/a[i][i];
    fo(i,1,n) sa[i]=i;
    sort(sa+1,sa+1+n,cmp2);
    fo(i,1,n) rrk[sa[i]]=i;
}
int main()
{
    freopen("t1.in","r",stdin);
    //freopen("graph.out","w",stdout);
    scanf("%d %d\n",&n,&m);
    srand(n*m-n+m);
    fo(i,1,m)
    {
        scanf("%d %d %d",&x,&y,&z);
        cr(x,y,z);
    }
    fo(i,1,n) rk[i]=i;
    random_shuffle(rk+1,rk+1+n);
    do
    {
        geteq();
        gauss();
        pp=1;
        fo(i,1,n) if (rrk[i]!=rk[i]) pp=0;
        fo(i,1,n) rk[i]=rrk[i];
    }while (!pp);
    printf("%.6lf\n",E[1]);
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值