「专题训练」游走(BZOJ-3143)

题意与分析

定义走到每条边的期望为(e_i),一开始的想法是给定一个(largesum_{i=1}^n e_i a_i),求一个a的排列使得这个和最小。问题在于这样等于没对题目作分析,而且题目的难度没有被转化降低。于是(在高人指点下)我们想到,如果先求出(e_i),然后按照从小到大的顺序贪心的编号,问题就直接转化成求走到每个边的期望。
边的期望是一个新操作,但是其实不难:定义走到点的期望是(f_i),考虑一条边((u, v)),对于这条边而言,只有从u和从v才能走到这条边,那么它的期望就是(large e_i=frac{f_u}{degree_u}+frac{f_v}{degree_v})。于是问题又转化成走到各个点的期望。
如果这是个DAG,那么问题是一个dp((dp[j])表示从j到n的期望长度,(dp[i] = sum_{i o j} frac{dp[j]+1}{degree_i}),具体问题见BZOJ-3036)。问题是这题并不是,场面有点尴尬。但是上面DAG的方程加起来共有n个,而我们要求n-1个解,这提示了我们用高斯消元解决这个问题。用高斯消元的话我们就把公式变形一下:

[e_i = sum_{j=1}^n p_{i,j}e_{j} , 2le i le n ]

其中(p_{i,j})表示从j走到i的概率,对于第一个点该式子的右边还要加上一个1(因为一开始就在这个点上了,不妨思考下平凡的情况)。这样一来就可以用高斯消元解决这一题了。

问题的逐步转化是这一题的关键。

代码

#include <bits/stdc++.h>

using namespace std;

vector<pair<int, int>> edges;
vector<int> G[505];

double arr[505][505];
int main() {
  int n, m;
  while (cin >> n >> m) {
    for (int i = 1, u, v; i <= m; ++i) {
      cin >> u >> v;
      if (u > v)
        swap(v, u);
      edges.push_back(make_pair(u, v));
      G[u].push_back(v);
      G[v].push_back(u);
    }
    memset(arr, 0, sizeof(arr));
    for (int i = 1; i <= n; ++i)
      arr[i][i] = -1.0;
    arr[1][n + 1] = -1.0;
    for (int i = 1; i < n; i++) {
      for (int j = 0; j < G[i].size(); ++j)
        arr[G[i][j]][i] += 1.0 / (int)G[i].size();
    }
    for (int i = 1; i <= n; ++i) {
      int idx = i;
      for (int j = i + 1; j <= n; ++j)
        if (fabs(arr[j][i]) > fabs(arr[idx][i]))
          idx = j;
      assert(fabs(arr[idx][i]) > 1e-10);
      if (idx != i)
        for (int j = i; j <= n + 1; ++j)
          swap(arr[i][j], arr[idx][j]);
      for (int j = 1; j <= n; ++j)
        if (i != j) {
          double t = arr[j][i] / arr[i][i];
          for (int k = i; k <= n + 1; ++k)
            arr[j][k] -= arr[i][k] * t;
        }
    }
    /*
    for (int i = 1; i <= n; ++i) {
      for (int j = 1; j <= n + 1; ++j) {
        cout << arr[i][j] << " ";
      }
      cout << endl;
    }
    */
    double e_node[505];
    double e_edge[500 * 500 / 2 + 5];
    memset(e_edge, 0, sizeof(e_edge));
    memset(e_node, 0, sizeof(e_node));
    for (int i = 1; i <= n; ++i) {
      e_node[i] = arr[i][n + 1] / arr[i][i];
    }
    for (int i = 0; i < edges.size(); ++i) {
      e_edge[i] += e_node[edges[i].first] / G[edges[i].first].size();
      if (edges[i].second != n)
	e_edge[i] += e_node[edges[i].second] / G[edges[i].second].size();
    }
    sort(e_edge, e_edge + m);
    double ans = 0;
    for (int i = 0; i < m; ++i)
      ans += e_edge[i] * (m - i);
    cout << fixed << setprecision(3) << ans << endl;
  }
  return 0;
}
原文地址:https://www.cnblogs.com/samhx/p/bzoj-3143.html