一个nn个点mm条边的无向连通图

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

现在,请你对这mm条边进行编号,使得小Z获得的总分的期望值最小。

n500n\le500

Luogu P3232

Solution

首先,根据排序不等式(或者贪心思想)可以发现,期望经过最多次数越多,分配的权值就要越小

那么转化成求每条边的期望经过次数

再转化一下,转化成求每个点的期望经过次数,即

p(x,y)=p[x]deg[x]+p[y]deg[y] p(x,y)=\frac{p[x]}{deg[x]}+\frac{p[y]}{deg[y]}

因为对于这条边上的两个点,都有度数分之一的概率走这条边

而每个点期望经过次数就是相邻点的期望除以它的度数,即

p[x]=yp[y]deg[x]+[x==1] p[x] = \frac{\sum_{y}p[y]}{deg[x]} + [x==1]

发现这个期望相互之间会有影响,高斯消元求解即可

需要注意的几个点:

  • 因为起点一开始第一次并不是从任何一个节点转移过来的,因此需要+1
  • 因为到了终点就会停止,所以与终点相连的边不需要考虑

  • 高斯消元找系数最大项是为了更方便判断无解(因为如果找不到系数>0>0的就无解)

    似乎也可以减小精度误差,但并没搞清楚原理,就按方便判无解记住吧

  • 浮点数比大小:(以前一直没搞清楚)

    感性理解一下的话就是如果a<ba<b,那么相对于很小epseps而言,aabb的差是远远大于它的,所以是<eps<-eps;而如果a=ba=b的话,有可能aabb大一点点,但只要在epseps误差范围内就能接受,所以是<eps<eps

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#include <bits/stdc++.h>

#define x first
#define y second
#define y1 Y1
#define y2 Y2
#define mp make_pair
#define pb push_back

using namespace std;

typedef long long LL;
typedef pair<int, int> pii;

template <typename T> inline int Chkmax (T &a, T b) { return a < b ? a = b, 1 : 0; }
template <typename T> inline int Chkmin (T &a, T b) { return a > b ? a = b, 1 : 0; }

inline void proc_status()
{
ifstream t ("/proc/self/status");
cerr << string (istreambuf_iterator <char> (t), istreambuf_iterator <char> ()) <<endl;
}

template <typename T> T read ()
{
T sum = 0, fl = 1; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') fl = -1;
for (; isdigit(ch); ch = getchar()) sum = (sum << 3) + (sum << 1) + ch - '0';
return sum * fl;
}

const int Maxn = 500 + 20, eps = 1e-8;

int N, M, e, Begin[Maxn], To[Maxn * Maxn], Next[Maxn * Maxn];
int deg[Maxn];
pii Edge[Maxn * Maxn];

inline void add_edge (int x, int y) { To[++e] = y; Next[e] = Begin[x]; Begin[x] = e; ++deg[x]; }

double P[Maxn];

namespace Gauss
{
double A[Maxn][Maxn];

inline void work ()
{
for (int i = 1; i < N; ++i)
{
int pos = i;
for (int j = i + 1; j < N; ++j) if (fabs(A[j][i]) - fabs(A[pos][i]) > eps) pos = j;
swap(A[pos], A[i]);
for (int j = i + 1; j <= N; ++j) A[i][j] /= A[i][i];
A[i][i] = 1;
for (int j = i + 1; j < N; ++j)
{
for (int k = i + 1; k <= N; ++k) A[j][k] -= A[j][i] * A[i][k];
A[j][i] = 0;
}
}

for (int i = N - 1; i >= 1; --i)
{
for (int j = i + 1; j < N; ++j) A[i][N] -= A[i][j] * P[j];
P[i] = A[i][N] / A[i][i];
}
}

inline void init ()
{
A[1][N] = 1;
for (int i = 1; i < N; ++i)
{
A[i][i] = 1;
for (int j = Begin[i]; j; j = Next[j])
{
int y = To[j];
if (y != N) A[i][y] = -1.0 / deg[y];
}
}
}
}

double Ans[Maxn * Maxn];

inline void Solve ()
{
Gauss :: init();
Gauss :: work();
for (int i = 1; i <= M; ++i)
{
if (Edge[i].x != N) Ans[i] += P[Edge[i].x] / deg[Edge[i].x];
if (Edge[i].y != N) Ans[i] += P[Edge[i].y] / deg[Edge[i].y];
}
sort(Ans + 1, Ans + M + 1);
double ans = 0;
for (int i = 1; i <= M; ++i)
ans += Ans[i] * (M - i + 1);
printf("%.3lf\n", ans);
}

inline void Input ()
{
N = read<int>(), M = read<int>();
for (int i = 1; i <= M; ++i)
{
int x = read<int>(), y = read<int>();
Edge[i].x = x, Edge[i].y = y;
add_edge (x, y);
add_edge (y, x);
}
}

int main()
{
#ifndef ONLINE_JUDGE
freopen("A.in", "r", stdin);
freopen("A.out", "w", stdout);
#endif
Input();
Solve();
return 0;
}

Debug

  • mm最大可能到maxnmaxnmaxn * maxn,一开始开小了