bzoj3143: [Hnoi2013]游走

传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3143

思路:首先贪心是很显然的,为了使分数最小,期望到达次数越多的,编号就应该给的越小。

直接设边的期望列方程比较复杂。

所以我们换一个思路,先解出每个点的期望到达次数,那么边的期望次数就可以算出来了。

设边为y,与它相连的两个点为a,b,点的期望次数为f[a],f[b],度数为deg[a],deg[b];

那么边的期望次数g[y]就是f[a]/deg[a]+f[b]/deg[b]。

这很好理解,因为一条边只会从这两个点走过来。

现在考虑求点

对于一个点x,它只会从相邻的点过来,那么方程就是

f[x]=∑f[son[x]]/deg[x];

除了n号点,我们就可以得到有n-1个未知数的n-1个方程了

高斯消元即可


#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
const int maxn=510,maxm=300010;
const double eps=1e-10;
using namespace std;
typedef	double ld;
int pre[maxm],now[maxn],son[maxm],tot,deg[maxn],n,m,st[maxm],ed[maxm];ld a[maxn][maxn],f[maxn],g[maxm],ans;
void add(int x,int y){pre[++tot]=now[x],now[x]=tot,son[tot]=y,deg[x]++;}

void gauss(){
	for (int i=1;i<=n;i++){
		int id=i;ld maxs=-1.0;
		for (int j=i;j<=n;j++) if (fabs(a[j][i])+eps>maxs) id=j,maxs=fabs(a[j][i]);
		if (id!=i) for (int j=1;j<=n+1;j++) swap(a[i][j],a[id][j]);
		ld t=a[i][i];if (fabs(t)<eps) for (;;);
		for (int j=1;j<=n+1;j++) a[i][j]/=t; 
		for (int j=1;j<=n;j++)
			if (j!=i){
				ld t=a[j][i];
				for (int k=1;k<=n+1;k++)
					a[j][k]-=t*a[i][k];
			}
	}
	for (int i=1;i<=n;i++) f[i]=a[i][n+1];
}

int main(){
	scanf("%d%d",&n,&m);
	for (int i=1,x,y;i<=m;i++)
		scanf("%d%d",&x,&y),add(x,y),add(y,x),st[i]=x,ed[i]=y;
	n--,a[1][n+1]=-1.0;
	for (int x=1;x<=n;x++){
		a[x][x]=-1.0;
		for (int y=now[x];y;y=pre[y])
			if (son[y]!=n+1)
				a[x][son[y]]=1.0/deg[son[y]];
	}
	gauss();
	for (int i=1;i<=m;i++) g[i]=f[st[i]]/deg[st[i]]+f[ed[i]]/deg[ed[i]];
	sort(g+1,g+1+m);
	for (int i=1;i<=m;i++) ans+=g[i]*(m-i+1);
	printf("%.3lf\n",(double)ans);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值