BZOJ_3143_[Hnoi2013]游走_期望DP+高斯消元

时间:2022-10-27 21:50:03

BZOJ_3143_[Hnoi2013]游走_期望DP+高斯消元

题意:

一个无向连通图,顶点从1编号到N,边从1编号到M。
小Z在该图上进行随机游走,初始时小Z在1号顶点,每一步小Z以相等的概率随机选 择当前顶点的某条边,沿着这条边走到下一个顶点,获得等于这条边的编号的分数。当小Z 到达N号顶点时游走结束,总分为所有获得的分数之和。
现在,请你对这M条边进行编号,使得小Z获得的总分的期望值最小。

分析:

题可以转化为求每条边被通过次数的期望。
每条边的期望等于两个端点被通过次数的期望乘上通过这条边的概率
每个点被通过次数的期望等于和它相邻的点的期望乘上到达这个点的期望
列出了几个方程,高斯消元求解。
需要注意的是
n这个点被通过次数等于0(因为不能从n到达任何点)
1这个点在列方程时常数项要加一(因为1是起点)

代码:

#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <queue>
#include <stdlib.h>
using namespace std;
#define du double
#define N 600
#define M 600000
int head[N],to[M],nxt[M],cnt,n,m;
int out[N];
du a[N][N],b[M];
inline void add(int u,int v){
to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;out[u]++;
}
du Abs(du x){
return x>0?x:-x;
}
void Gauss(){
int i,j,k;
for(i=1;i<=n;i++){
int mx=i;
for(j=i+1;j<=n;j++){
if(Abs(a[j][i])>Abs(a[mx][i]))mx=j;
}
if(mx!=i){
for(j=i;j<=n+1;j++){
swap(a[i][j],a[mx][j]);
}
}
for(j=i+1;j<=n;j++){
du tmp=-a[j][i]/a[i][i];
a[j][i]=0;
for(k=i+1;k<=n+1;k++){
a[j][k]+=tmp*a[i][k];
}
}
}
for(i=n;i;i--){
for(j=i+1;j<=n;j++){
a[i][n+1]-=a[j][n+1]*a[i][j];
}
a[i][n+1]/=a[i][i];
}
}
int main(){
scanf("%d%d",&n,&m);
int i,j,x,y;
for(i=1;i<=m;i++){
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
for(i=1;i<n;i++){
if(i==1){
a[i][i]=1;a[i][n+1]=1;
}else a[i][i]=1;
for(j=head[i];j;j=nxt[j]){
a[i][to[j]]=-1.0/out[to[j]];
}
}
a[n][n]=1;
Gauss();
//for(i=1;i<=n;i++)printf("%.2lf\n",a[i][n+1]);
for(i=1;i<=cnt;i+=2){
x=to[i],y=to[i+1];
b[i+1>>1]=a[x][n+1]/out[x]+a[y][n+1]/out[y];
}
sort(b+1,b+m+1);
du ans=0;
for(i=1;i<=m;i++){
ans+=b[i]*(m-i+1);
}
printf("%.3lf\n",ans);
}