题解 P3232 [HNOI2013]游走

洛谷P3232[NOI2013]游走

题目描述

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

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

输入格式

第一行是两个整数,分别表示该图的顶点数 n 和边数 m。

接下来 m 行每行两个整数 u,v表示顶点 u 与顶点 v 之间存在一条边。

输出格式

输出一行一个实数表示答案,保留三位小数。

输入输出样例

输入

3 3
2 3
1 2
1 3

输出

3.333

说明提示

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

题解

正解的做法应该是期望dp+高斯消元QAQ

第一次接触到这样的题,我也是鼓捣了好半天

要想总体的期望值最小,就必须让经过次数越多的边的编号越小

那么这道题的主要部分就是搞每条边的期望经过次数

问题来了,边的期望并不好求,但是点的好求!!

用du[i]为点i的出度或入度,num[i]表示i点的期望经过值,f(u,v)为期望经过值

\(f(u,v)=\frac{num[u]} {du[u]}+\frac{num[v]} {du[v]}\)

求一个点的期望经过值就要用到高斯消元了

设s[i][j]为第i个方程第j项的系数

每个点经过自己的系数定为1;

\(s[i][i]=1\)

如果某个点与i相连接那么他的系数就是-1/该点的出度

\(s[i][j]=-\frac{1}{du[j]}\)

其他的系数就是0了

特殊的对于n而言,经过他就不会在出去,所以第n个方程处第n项外其余系数均为0

点1与点n一定会被经过,所以只有这两个式子的值是1,其余均为0,为了方便计算,我们把第i个式子的值存到s[i][n+1]上。

$s[i][n+1]= \sum_{j=1}^n x_j*s[i][j] $

最后在排个序就OK了

Code

#include<bits/stdc++.h>//万能头
#define ll long long
using namespace std;
const int M=125e3;
int n,m,sum,tot,head[2*M+5],ver[2*M+5],nxt[2*M+5],fro[2*M+5];
double du[505],s[505][505],num[505],ans;
priority_queue<double> q;//利用优先队列排序
void add(int u,int v)//邻接表存边
{
	ver[++tot]=v;
	fro[tot]=u;
	nxt[tot]=head[u];
	head[u]=tot;
}
void init()//初始化
{
	scanf("%d%d",&n,&m);
	for(int i=1,u,v;i<=m;i++)
	{
		scanf("%d%d",&u,&v);
		add(u,v); add(v,u);
		du[u]++; du[v]++;
	}
}
void gauss()//高斯消元 
{
	for(int i=1;i<=n;i++)//正推
	{
		int pos=0;
		for(int j=1;j<=n;j++)
			if(s[i][j])
			{
				pos=j;
				break;
			}
		if(s[i][pos]!=1&&s[i][pos])
			for(int j=n+1;j>=pos;j--)
				s[i][j]/=s[i][pos];
		for(int j=i+1;j<=n;j++)
		{
			if(!s[j][pos])
				continue;
			for(int k=n+1;k>=pos;k--)
				s[j][k]-=s[i][k]*s[j][pos];
		}
	}
	
	for(int i=n;i>=2;i--)//逆推
	{
		int pos=0;
		for(int j=1;j<=n;j++)
			if(s[i][j])
			{
				pos=j;
				break;
			}
		if(s[i][pos]!=1&&s[i][pos])
			for(int j=n+1;j>=pos;j--)
				s[i][j]/=s[i][pos];
		for(int j=1;j<i;j++)
		{
			if(!s[j][pos])
				continue;
			for(int k=n+1;k>=pos;k--)
				s[j][k]-=s[i][k]*s[j][pos];
		}
	}
}
int main()
{
	init();//初始化
	for(int i=1;i<=n;i++)
		for(int j=head[i];j;j=nxt[j])
		{
			int from=ver[j];
			if(from!=n)//因为n不可能出来,所以第i个xn系数一定为0
				s[i][from]=-1/du[from];
		}
	for(int i=1;i<=n;i++)
		s[i][i]=1;
	for(int i=1;i<n;i++)
		s[n][i]=0;
	s[n][n+1]=s[1][n+1]=1;
	gauss();//高斯消元
	for(int i=1;i<=n;i++)//记录xi的值
		for(int j=1;j<=n;j++)
			if(s[i][j]==1)
			{
				num[j]=s[i][n+1];
				break;
			}
	for(int i=1;i<tot;i+=2)//计算每条边的期望经过值
	{
		int x=fro[i],y=ver[i];
		q.push(num[x]/du[x]*(x!=n)+num[y]/du[y]*(y!=n));
	}
	while(!q.empty())//优先队列排序
	{
		sum++;
		ans+=sum*q.top();
		q.pop();
	}
	printf("%.3lf",ans);
	return 0;
}

第一次用LaTeX,不熟练,望包涵orz

posted @ 2021-05-05 11:57  Varuxn  阅读(163)  评论(0编辑  收藏  举报