luoguP3232 [HNOI2013]游走
有后效性的期望dp。
\(Description\)
给定一张\(n\)个点\(m\)条边的无向图,无重边自环,从1号点出发,每一次以相等的概率去向相连的一个点,到\(n\)号点停止行动。给每条边赋一个权值,权值\(\in [1,m]\),不能重复。
求所有边的边权与期望经过次数的乘积的最小值。
\(Solution\)
直接考虑期望dp
有后效性,于是乎考虑列出方程,求解每个状态
直接考虑计算边的期望的话,复杂度会爆炸
所以考虑一个转换
对于一条边\(i\),设其端点为\(u,v\),然后端点期望经过次数分别为\(f_u,f_v\),那么其期望经过次数\(g_i\)为
\[g_i=\frac{f_u}{k_u}+\frac{f_v}{k_v}
\]
其中\(k_x\)为点x的度数
那么问题转换成如何求\(f\),求完排个序赋个值就完了,那么显然有转移式
\[f_u=\sum_{v,<u,v>\in E} \frac{f_v}{k_v}
\]
无向图,有后效性,我们可以将递推式转换为\(n\)个(实际上是\(n-1\)个)线性方程利用高斯消元求解每个\(f\)
还有一点值得注意的是,我们到点\(n\)后,就不会游走了,所以点\(n\)对答案没有贡献,不考虑点\(n\)即可。
时间复杂度\(O(n^3)\)
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
#define rep(i,x,y) for(int i=x;i<=y;i++)
#define _rep(i,x,y) for(int i=x;i>=y;i--)
#define int long long
#define N 505
#define M 200010
const int inf=1e18;
const double eps=1e-8;
inline int read()
{
int num=0,fu=1; char c=getchar();
while(c!='-'&&(c>'9'||c<'0')) c=getchar();
if(c=='-') fu=-1,c=getchar();
while(c>='0'&&c<='9') num=(num<<3)+(num<<1)+(c^48),c=getchar();
return fu*num;
}
int n,m,u[M],v[M],k[M];
double B[N][N],cnt[N],ans;
void gauss(int n)
{
rep(i,1,n)
{
int mx=i;
rep(j,i+1,n)
if(fabs(B[j][i])-fabs(B[mx][i])>eps) mx=j;
if(mx!=i)
rep(j,1,n+1) swap(B[mx][j],B[i][j]);
rep(j,i+1,n+1) B[i][j]/=B[i][i]; B[i][i]=1;
rep(j,1,n)
{
if(j==i) continue;
rep(k,i+1,n+1) B[j][k]-=B[i][k]*B[j][i];
B[j][i]=0;
}
}
}
signed main()
{
n=read(); m=read();
rep(i,1,m)
{
u[i]=read(); v[i]=read();
k[u[i]]++; k[v[i]]++;
}
rep(i,1,m)
{
if(u[i]==n||v[i]==n) continue;
B[u[i]][v[i]]=1.0/(double)k[v[i]];
B[v[i]][u[i]]=1.0/(double)k[u[i]];
}
rep(i,1,n-1) B[i][i]=-1;
B[1][n]=-1;
gauss(n-1);
rep(i,1,m) cnt[i]=B[u[i]][n]/k[u[i]]+B[v[i]][n]/k[v[i]];
sort(cnt+1,cnt+1+m);
rep(i,1,m) ans+=cnt[i]*(double)(m-i+1);
printf("%.3lf\n",ans);
return 0;
}

浙公网安备 33010602011771号