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;
}
posted @ 2021-03-07 10:53  Ing1024  阅读(18)  评论(0)    收藏  举报