【YBTOJ】【Luogu P3232】[HNOI2013]游走
链接:
题目描述:
给定一个 \(n\) 个点 \(m\) 条边的无向连通图,顶点从 \(1\) 编号到 \(n\),边从 \(1\) 编号到 \(m\)。
小 Z 在该图上进行随机游走,初始时小 Z 在 \(1\) 号顶点,每一步小 Z 以相等的概率随机选择当前顶点的某条边,沿着这条边走到下一个顶点,获得等于这条边的编号的分数。当小 Z 到达 \(n\) 号顶点时游走结束,总分为所有获得的分数之和。 现在,请你对这 \(m\) 条边进行编号,使得小 Z 获得的总分的期望值最小。
\(2\leq n \leq 500,1 \leq m \leq 125000\)。
正文:
边的数量太大,所以我们不能以直接求边的期望经过次数。那我们就求每个点的。设 \(f_i\) 表示第 \(i\) 个点的期望经过次数。那么第 \(i\) 条边的期望经过次数 \(g_i\) 就很好求了:
\[g_i=\frac{f_u}{deg_u}+\frac{f_v}{deg_v}
\]
其中 \(deg_i\) 表示第 \(i\) 个点的度数。
现在我们的问题是如何求 \(f_i\) 了。
容易得到:
\[f_i=\sum_{(i,j)\in E,j\not=n}\frac{f_j}{deg_u}+(i==1)
\]
如果我们直接 DFS,肯定是过不了的。可以用高斯消元。
首先 \(1\) 号点到 \(n\) 点的概率肯定是 \(1\),其次是点 \(i\) 的期望次数减去其它点转移过来的期望次数肯定是 \(0\)。
因为到 \(n\) 停止游走,不能考虑 \(n\),这样就能构成 \(n-1\) 个线性方程组,可以开搞高消了。
最后将 \(g_i\) 倒序排序,就能得到答案了。
代码:
int n, m;
int deg[N];
double f[N], a[N][N], g[M], ans;
struct edge
{
int from, to, nxt;
}e[M << 1];
int head[N], tot;
void add (int u, int v)
{
e[++tot] = (edge){u, v, head[u]}, head[u] = tot;
}
void Gauss(int n)
{
for (int i = 1; i <= n; i++)
{
int mxi = i;
for (int j = i + 1; j <= n; j++)
if(fabs(a[mxi][i]) < fabs(a[j][i])) mxi = j;
swap(a[mxi], a[i]);
double inv = a[i][i];
for (int j = i; j <= n + 1; j++)
a[i][j] /= inv;
for (int j = i + 1; j <= n; j++)
{
inv = a[j][i];
for (int k = i; k <= n + 1; k++)
a[j][k] -= a[i][k] * inv;
}
}
f[n] = a[n][n + 1];
for (int i = n - 1; i; --i)
{
for (int j = i + 1; j <= n; ++j)
a[i][n + 1] -= f[j] * a[i][j];
f[i] = a[i][n + 1] / a[i][i];
}
}
int main()
{
scanf ("%d%d", &n, &m);
for (int i = 1; i <= m; i++)
{
int x, y;
scanf ("%d%d", &x, &y);
add (x, y), add(y, x);
deg[x]++, deg[y]++;
}
for (int i = 1; i < n; i++)
{
a[i][i] = 1.0;
for (int j = head[i]; j; j = e[j].nxt)
{
int v = e[j].to;
if (v != n) a[i][v] -= 1.0 / deg[v];
}
}
a[1][n] = 1;
Gauss (n - 1);
for (int i = 1; i <= m; i++)
g[i] = f[e[i<<1].from] / deg[e[i<<1].from] + f[e[i<<1].to] / deg[e[i<<1].to];
sort (g + 1, g + 1 + m);
for (int i = 1; i <= m; i++)
ans += g[i] * (m - i + 1.0);
printf ("%.3lf", ans);
return 0;
}