P3232 [HNOI2013]游走——无向连通图&&高斯消元

题意

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

分析

直接算边的期望会很大,考虑先算点的期望。

设 $E(i)$ 为经过第 $i$ 个点的期望次数,$D(i)$ 为 $i$ 的度数,设 $v$ 为与 $u$ 相连的点,则

$$E(u) = \sum_{v, v \neq n} \frac{E(v)}{D(v)}$$

从而,经过一条边 $(u, v)$ 的期望次数为

$$E(u, v)= \frac{E(u)}{D(u)} + \frac{E(v)}{D(v)}$$

有两点需要注意,结点1期望步数需要加1(可假设有结点0,以概率1转移到结点1);由于到结点 $n$ 就结束了,不要考虑来自 $n$ 的步数。

由于是无向连通图,不用考虑0和无穷大,方程组有唯一的解。

既然求出每条边的期望次数,当然是给次数大的分配小编号,次数小的分配大编号。

#include<bits/stdc++.h>
using namespace std;

const int maxn = 500+10;
typedef double Matrix[maxn][maxn];

//要求系数矩阵可逆
//这里的A是增广矩阵,即A[i][n] 是第i个方程右边的常数bi
//运行结束后A[i][n] 是第i个未知数的值
void gauss_elimination(Matrix A, int n)
{
    int i, j, k, r;

    for(i = 0;i < n;i++)  //消元过程
    {
        //选绝对值一行r并与第i行交换
        r = i;
        for(j = i+1; j < n;j++)
            if(fabs(A[j][i] > fabs(A[r][i]))) r = j;
        if(r != i) for(j = 0;j <= n;j++)  swap(A[r][j], A[i][j]);

        //与第i+1~n行进行消元
        for(k = i+1; k < n;k++)
        {
            double f = A[k][i] / A[i][i];
            for(int j = i;j <= n;j++)  A[k][j] -= f * A[i][j];      //已经是阶梯型矩阵了,所以从i开始
        }
    }

    //回代过程
    for(i = n-1;i >= 0;i--)
    {
        for(j = i+1; j < n;j++)
            A[i][n] -= A[j][n] * A[i][j];
        A[i][n] /= A[i][i];
    }
}

void debug_print(Matrix A, int n)
{
    for(int i = 0;i < n;i++)
        for(int j = 0;j <= n;j++)  printf("%f%c", A[i][j], j == n ? '\n' : ' ');
}

int n, m;
vector<int>edges[maxn];
int d[maxn];
Matrix  A;
vector<double>e;

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 0;i < m;i++)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        u--; v--;   //改成从0开始编号
        edges[u].push_back(v);
        edges[v].push_back(u);
        d[u]++; d[v]++;
    }

    //构造方程组
    for(int i = 0;i <n;i++)
    {
        A[i][i] = 1;
        for(int j = 0;j <edges[i].size();j++)
            if(edges[i][j] != n-1)  A[i][edges[i][j]] -= 1.0/d[edges[i][j]];
        if(i == 0)  A[i][n] = 1;
    }

    //debug_print(A, n);

    gauss_elimination(A, n);

    for(int i = 0;i < n;i++)
    {
        for(int j = 0;j < edges[i].size();j++)
        {
            double tmp = 0;
            if(i != n-1)  tmp += A[i][n]/d[i];
            if(edges[i][j] != n-1)  tmp +=  + A[edges[i][j]][n]/d[edges[i][j]];  //不是终点时
            e.push_back(tmp);
        }
    }

    sort(e.begin(), e.end());

//    for(int i = 0;i < e.size();i++)  printf("%f ", e[i]);
//    printf("\n");

    double res = 0;
    for(int i = 0;i < m;i++)
    {
        res += e[2*i]*(m-i);  //无向边重复了一次,所以隔一个取一个
    }
    printf("%.3f\n", res);
}

参考链接:https://www.luogu.org/problemnew/solution/P3232

猜你喜欢

转载自www.cnblogs.com/lfri/p/11543107.html