bzoj3143 游走 [高斯消元+概率]

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。

Solution

这道题要求编号后期望的最小得分,为了使得分最小,那么就应该让出现期望大的边编号尽量小.这样我们就把问题转换成了求边的期望出现概率,然后贪心地编号即可.我们接着考虑,如何求边的期望出现概率呢?我们可以通过求出这两条边端点的出现概率除以各自度数然后加和,于是我们顺利把这道题转换成了求点出现概率的题目.对每个点列方程,高斯消元求解既可,需要特别注意的是起始点求概率时要+1,因为它是一定会出现的!

Code

#include<bits/stdc++.h>
#define maxn 3010
#define eps 1e-6
// #define COGS
// #define DEBUG
using namespace std;
int n,m,num_edge=0;
int edge[maxn][maxn];
double answer=0;
double a[maxn][maxn],ans[maxn],deg[maxn];

inline int dcmp(double x)
{
  if(abs(x)<=eps) return 0;
  else return x>0?1:-1;
}

inline int read()
{
  char ch;
  int read=0,sign=1;
  do
    ch=getchar();
  while((ch<'0'||ch>'9')&&ch!='-');
  if(ch=='-') sign=-1,ch=getchar();
  while(ch>='0'&&ch<='9')
  {
    read=read*10+ch-'0';
    ch=getchar();
  }
  return sign*read;
}

void pre_work()
{
  n=read(),m=read();n--;
  for(int i=1,u,v;i<=m;++i) u=read(),v=read(),edge[u][v]=edge[v][u]=1,deg[u]++,deg[v]++;
  for(int i=1;i<=n;++i)
  {
    a[i][i]=1,deg[i]=1.0/deg[i];
  }
  for(int i=1;i<=n;++i)
    for(int j=1;j<=n+1;++j)
      if(edge[i][j])
        a[j][i]-=deg[i];
  a[1][n+1]=1;
#ifdef DEBUG
  cerr<<"begin"<<endl;
  for(int i=1;i<=n;++i){
    for(int j=1;j<=n+1;++j)
      cerr<<a[i][j]<<" ";
    cerr<<endl;}
#endif
}

void print()
{
  int cnt=0;
  for(int i=1;i<=n;++i)
    for(int j=1;j<=n+1;++j)
      if(edge[i][j])
      {
        ans[++cnt]=a[i][n+1]*deg[i];
        ans[cnt]+=a[j][n+1]*deg[j];
        edge[j][i]=0;
      }
  sort(ans+1,ans+cnt+1);
#ifdef DEBUG
  cerr<<"ans[]:"<<endl;
  for(int i=1;i<=cnt;++i) cerr<<ans[i]<<endl;
#endif
  for(int i=1;i<=cnt;++i) answer+=ans[i]*(cnt-i+1);
  printf("%.3lf\n",answer);
}

void gauss()
{
  for(int i=1;i<=n;++i)
  {
    if(!dcmp(a[i][i]))
    {
      int k=i;
      while(!dcmp(a[k][k])) k++;
      swap(a[k],a[i]);
    }
    for(int j=i+1;j<=n;++j)
    {
      for(int k=i+1;k<=n+1;++k)
        a[j][k]-=a[i][k]*(a[j][i]/a[i][i]);
      a[j][i]=0;
    }
  }
  for(int i=n;i>1;--i)
  {
    for(int j=1;j<i;++j)
    {
      a[j][n+1]-=a[i][n+1]*(a[j][i]/a[i][i]);
      a[j][i]=0;
    }
    a[i][n+1]/=a[i][i];
  }
#ifdef DEBUG
  cerr<<"after"<<endl;
  for(int i=1;i<=n;++i){
    for(int j=1;j<=n+1;++j)
      cerr<<a[i][j]<<" ";
    cerr<<endl;}
#endif
  print();
}

int main()
{
#ifdef COGS
  freopen("walk.in","r",stdin);
  freopen("walk.out","w",stdout);
#endif
  pre_work();
  gauss();
  return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值