BZOJ 3143 [Hnoi2013] 游走

216 篇文章 0 订阅
38 篇文章 0 订阅

Description

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

Input

第一行是正整数N和M,分别表示该图的顶点数 和边数,接下来M行每行是整数u,v(1≤u,v≤N),表示顶点u与顶点v之间存在一条边。 输入保证30%的数据满足N≤10,100%的数据满足2≤N≤500且是一个无向简单连通图。

Output

仅包含一个实数,表示最小的期望值,保留3位小数。

Sample Input

3 3
2 3
1 2
1 3

Sample Output

3.333

HINT

(1,2) 编号为 1 ,边 (1,3) 编号 2 ,边 (2,3) 编号为 3

Source

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

高斯消元+贪心+神奇的思路+期望~

首先,我们可以用贪心来求解,因为边权对每条边的期望行走次数无影响,所以我们把经常经过的边标为小号,反之标为大号。

这道题里面的图可能有环,所以一般的期望DP是不行的。我们可以列出期望之间的方程来求解。

但是注意到边又多又不容易找关系,所以我们以点为依据列方程。用a[i]表示i点的经过次数期望值,那么容易列出方程a[i]=a[1]*k[1][i]+a[1]*k[2][i]+…+a[n-1]*k[n-1][i](没有i),其中k[i][j]表示从i点走到j点的概率(预处理得出,后面写求它的方法),注意其中没有n点,因为走到n点就结束;而且i==1时要多一个常数项1,因为1点在开始的时候就已经走过一次。这样,我们把所有含未知数的项移到等式右面,就成了n-1个方程,n-1个未知数,显然有唯一解,用高斯消元就可以了~

预处理出每个点的度数du[i]。

k[i][j]从i点走到j点的概率)的求法:i、j不相连时为0,相连时k[i][j]=1.0/du[i]。

最后,已知所有点的期望值,则一条边(x,y)的期望经过次数为val[i]=a[x]/du[x]+a[y]/du[y]。

按照上面的写就好了~

注意:所有1都要写成1.0!不然会WA得很惨!(知道真相的我眼泪掉下来)


#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

int n,m,du[501];
double k[501][501],a[501][501],val[125001],ans;
bool b[501][501];

struct node{
	int x,y;
}aa[125001];

int read()
{
	int totnum=0;char ch=getchar();
	while(ch<'0' || ch>'9') ch=getchar();
	while(ch>='0' && ch<='9') {totnum=(totnum<<1)+(totnum<<3)+ch-'0';ch=getchar();}
	return totnum;
}

void guass()
{
	for(int i=1;i<n;i++)
	{
		for(int j=i+1;j<=n+1;j++) a[i][j]/=a[i][i];a[i][i]=1;
		for(int j=i+1;j<=n;j++)
		{
			for(int k=i+1;k<=n+1;k++) a[j][k]-=a[j][i]*a[i][k];
			a[j][i]=0;
		}
	}
	for(int i=n;i>1;i--)
	{
		for(int j=i+1;j<=n+1;j++) a[i][j]/=a[i][i];a[i][i]=1;
		for(int j=i-1;j;j--)
		{
			for(int k=i+1;k<=n+1;k++) a[j][k]-=a[j][i]*a[i][k];
			a[j][i]=0;
		}
	}
}

int main()
{
	n=read();m=read();
	for(int i=1;i<=m;i++)
	{
		aa[i].x=read();aa[i].y=read();
		du[aa[i].x]++;du[aa[i].y]++;b[aa[i].x][aa[i].y]=b[aa[i].y][aa[i].x]=1;
	}
	n--;a[1][n+1]=-1.0;
	for(int i=1;i<n;i++)
	  for(int j=i+1;j<=n;j++) if(b[i][j]) k[i][j]=1.0/du[i],k[j][i]=1.0/du[j];
	for(int i=1;i<=n;i++)
	  for(int j=1;j<=n;j++)
	    if(i==j) a[i][j]=-1.0;
	    else a[i][j]=k[j][i];
	guass();
	for(int i=1;i<=m;i++) val[i]=a[aa[i].x][n+1]/du[aa[i].x]+a[aa[i].y][n+1]/du[aa[i].y];
	sort(val+1,val+m+1);
	for(int i=1;i<=m;i++) ans+=val[m-i+1]*(double)i;
	printf("%.3lf\n",ans);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值