Loading

题解 lg3232 [HNOI2013]游走

题意

给定一个 n个点 m 条边的无向连通图,顶点从 1 编号到 n,边从 1编号到 m。

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

思路

首先,一个较为显然的性质, 获得的总分的期望值 就是 每一条边期望经过的次数 乘上 其权值

然后贪心地将小权值赋给期望次数大的边

但边数实际上最大会有\(125000\),会跑不过,我们此时运用技巧点边转换,考虑点的期望经过次数与边的关系,得到 边的期望经过次数 = $$f_u \frac{1}{deg_v} + f_v \frac{1}{deg_v}$$

其中\(deg\)为点的度数,\(u,v\)分别为一条边的两个端点,\(f\)为点的期望经过次数

然后考虑点,对于点n,它不会被经过,因为到它就停了,于是有以下式子

\[f_i=\sum_{e(j,i)\in E,j\neq n} \frac{f_j}{d_j} \]

注意,当\(i=1\)时,需特判为\(f_1=\sum_{e(j,1)\in E,j\neq n} \frac{f_j}{d_j}+1\),因为从1开始.

然后就发现不能递推QAQ

但是这可以转换一个n-1元1次方程组,于是高斯消元

代码

#include<bits/stdc++.h>
using namespace std;
int n,m;
int d[510];
double a[510][510],c[510],val[125010],x[510];
double ans=0.0;
struct edge{
	int x,y;
}e[125010];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d%d",&e[i].x,&e[i].y);
		d[e[i].x]++,d[e[i].y]++;
	}
	for(int i=1;i<n;i++)a[i][i]=1.0,c[i]=0.0;
	for(int i=1;i<=m;i++){
		if(e[i].x==n || e[i].y==n)continue;
		a[e[i].x][e[i].y]=-1.0/d[e[i].y];
		a[e[i].y][e[i].x]=-1.0/d[e[i].x];
	}
	c[1]=1.0;
	for(int i=1;i<=n-1;i++){
		int maxx=i;
		for(int j=i+1;j<=n-1;j++)
			if(fabs(a[maxx][i])<fabs(a[j][i]))maxx=j;
		if(maxx!=i)swap(a[i],a[maxx]),swap(c[i],c[maxx]);
		for(int j=1;j<=n-1;j++){
			if(j==i)continue;
			double rate=a[j][i]/a[i][i];
			for(int k=i;k<=n-1;k++)a[j][k]-=rate*a[i][k];
			c[j]-=rate*c[i];
		}
	}
	for(int i=1;i<=n-1;i++)x[i]=c[i]/a[i][i];
	for(int i=1;i<=m;i++){
		if(e[i].x!=n)val[i]+=1.0*x[e[i].x]/d[e[i].x];
		if(e[i].y!=n)val[i]+=1.0*x[e[i].y]/d[e[i].y];
	}
	sort(val+1,val+m+1);
	for(int i=1;i<=m;i++){
		ans+=(m-i+1)*val[i];
	}
	printf("%.3lf\n",ans);
	return 0;
}
posted @ 2020-11-18 10:36  fpjo  阅读(128)  评论(0编辑  收藏  举报