[HNOI2013] 游走
题意:
一张无向图,从点1开始随机游走,走到点n时结束。每走一条边会得到等同于边权的收益。
(随机游走:每次等概率选择一条当前点的出边走过去)
请你给m条边分配边权(边权是1-m的排列),使得期望收益最小。
$n\leq 500$。
题解:
令期望收益为S,边$(u,v)$的边权为$w_{u,v}$,期望经过次数为$cnt_{u,v}$,则有$S=\sum cnt_{u,v}\times w_{u,v}$。
发现$cnt_{u,v}$并不好求,但如果知道了每个点u的期望经过次数$F_u$和度数$D_u$,就有$cnt_{u,v}=\frac{F_u }{D_u }+\frac{F_v }{D_v }$。
而$F_1 =1+\sum \frac{F_v }{D_v },F_u =\sum \frac{F_v }{D_v }$,于是可以用高斯消元解出所有的F,然后反推cnt,倒序分配边权即可。
注意点n是不能走出去的,所以列方程时需要特判。
复杂度$O(n^{3})$。
套路:
- 求边经过次数$\rightarrow$求点经过次数。
- 随机游走问题:在列方程时注意终点是不能往外走的。
代码:
#include<bits/stdc++.h> #define maxn 505 #define maxm 500005 #define inf 0x7fffffff #define ll long long #define rint register int #define debug(x) cerr<<#x<<": "<<x<<endl #define fgx cerr<<"--------------"<<endl #define dgx cerr<<"=============="<<endl using namespace std; int D[maxm],G[maxn][maxn]; double A[maxn][maxn],res[maxm],F[maxm]; inline int read(){ int x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } inline void Gauss(int n){ for(int j=1;j<=n;j++){ for(int i=j;i<=n;i++) if(A[i][j]!=0){ for(int k=1;k<=n+1;k++) swap(A[i][k],A[j][k]); break; } if(A[j][j]==0) continue; for(int i=1;i<=n;i++) if(i!=j && A[i][j]!=0){ double x=A[i][j]/A[j][j]; for(int k=1;k<=n+1;k++) A[i][k]-=A[j][k]*x; } } } int main(){ int n=read(),m=read(),tot=0; for(int i=1;i<=m;i++){ int u=read(),v=read(); G[u][v]=G[v][u]=1,D[u]++,D[v]++; } for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ if(i==j) A[i][j]=1; else if(j==n) A[i][j]=0; else if(G[i][j]) A[i][j]=-1.0/(D[j]+0.0); else A[i][j]=0; } A[i][n+1]=(i==1)?1:0; } Gauss(n); for(int i=1;i<=n;i++) F[i]=A[i][n+1]/A[i][i]; for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++){ if(!G[i][j]) continue; if(j==n) res[++tot]=F[i]/(D[i]+0.0); else res[++tot]=F[i]/(D[i]+0.0)+F[j]/(D[j]+0.0); } sort(res+1,res+1+tot); double ans=0; for(int i=1;i<=m;i++) ans+=res[i]*(m-i+1); printf("%.3lf\n",ans); return 0; }