BZOJ 3143: [Hnoi2013]游走 概率与期望+高斯消元
Description
一个无向连通图,顶点从1编号到N,边从1编号到M。
小Z在该图上进行随机游走,初始时小Z在1号顶点,每一步小Z以相等的概率随机选 择当前顶点的某条边,沿着这条边走到下一个顶点,获得等于这条边的编号的分数。当小Z 到达N号顶点时游走结束,总分为所有获得的分数之和。
现在,请你对这M条边进行编号,使得小Z获得的总分的期望值最小。
Input
第一行是正整数N和M,分别表示该图的顶点数 和边数,接下来M行每行是整数u,v(1≤u,v≤N),表示顶点u与顶点v之间存在一条边。 输入保证30%的数据满足N≤10,100%的数据满足2≤N≤500且是一个无向简单连通图。
Output
仅包含一个实数,表示最小的期望值,保留3位小数。
给定一个简单无向连通图,求给每个边分配一个边权,使得从 $1$ 号点到 $n$ 号点经过边权和的期望值最小. (分配的边权互不相同,且值域为 $[1,m]$).
首先有一个结论,$1$ 到 $n$ 号点权和的期望值为 $\sum_{i=1}^{m}val[i]\times perc[i],$即每条边的边权乘以走这条边的概率.
然后,我们发现经过每条边的概率只与这条边两个端点有关,即分别走到两个端点的概率以及两个端点的度数.
具体地,我们可以列出这样的方程组:($f[i]$ 表示从起点走到 $i$ 的概率)
$f[i]=\sum f[from]\times deg[from].$
如果这是一个 $DAG,$ 则可以直接来一遍裸的 dp,然而由于有环的存在,只能用高斯消元.
即将式子变为 $f[i]-\sum f[from]\times deg[from]=0.$
可以将这个看作是关于 $i$ 的一个方程组,列出方程矩阵之后解一下即可.
特别的,由于肯定会经过 $1$ 号点,所以高斯消元中 $1$ 所对应的那个方程组的常数项为 $1.$
其实呢,你也可以把这个概率理解为期望经过该点的次数.
而你发现在这个情况下这个期望出现的次数和概率是等价的.
#include <cmath> #include <cstdio> #include <algorithm> #define N 503 #define M 205089 #define setIO(s) freopen(s".in","r",stdin) using namespace std; double f[N][N],G[M]; int edges; int hd[N],to[M<<1],nex[M<<1],deg[N],U[M],V[M]; void add(int u,int v) { nex[++edges]=hd[u],hd[u]=edges,to[edges]=v; } void Gauss(int n) { int i,j,k,now; for(i=1;i<=n;++i) { now=i; for(j=i;j<=n;++j) { if(fabs(f[j][i])>fabs(f[now][i])) now=j; } if(now!=i) { for(j=1;j<=n;++j) swap(f[i][j],f[now][j]); } if(f[i][i]) { for(j=i+1;j<=n+1;++j) f[i][j]/=f[i][i]; f[i][i]=1; } for(j=i+1;j<=n;++j) { double div=f[j][i]; for(k=i+1;k<=n+1;++k) f[j][k]-=div*f[i][k]; f[j][i]=0; } } for(i=n;i>=1;--i) { for(j=i+1;j<=n;++j) { f[i][n+1]-=f[j][n+1]*f[i][j]; } } } int main() { int i,j,n,m; // setIO("input"); scanf("%d%d",&n,&m); for(i=1;i<=m;++i) { int u,v; scanf("%d%d",&u,&v),add(u,v),add(v,u),++deg[u],++deg[v],U[i]=u,V[i]=v; } f[1][n]=1.0; for(i=1;i<n;++i) { f[i][i]=1; for(j=hd[i];j;j=nex[j]) { int v=to[j]; if(v==n) continue; f[i][v]=-1.0/deg[v]; } } Gauss(n-1); for(i=1;i<=m;++i) { int a=U[i],b=V[i]; if(a!=n) { G[i]+=f[a][n]*(1.0/deg[a]); } if(b!=n) { G[i]+=f[b][n]*(1.0/deg[b]); } } sort(G+1,G+1+m); double ans=0; for(i=1;i<=m;++i) { ans+=G[i]*((m-i+1)*1.0); } printf("%.3lf\n",ans); return 0; }