1. 程式人生 > 實用技巧 >題解 lg3232 [HNOI2013]遊走

題解 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;
}