题面
分析
设 表示从 走到 的期望步数, 表示 能到的点的集合,那么有 理解这个式子,就考虑从 到 有 的概率,并且若从 到了 ,再到 ,步数就会多 。
对于每个点得到这个式子,正常情况下我们直接高斯消元解得每个 ,但是这里 ,高斯消元 ,GG。于是我们观察到保证强连通分量的大小不超过 这个条件,由于强联通分量外的 值与强连通分量内的 值没有相互包含关系,只有单方向的递推关系,所以我们按照拓扑序倒着对每个强连通分量进行高斯消元,就能保证对当前强连通分量消元时,所有相关的其他 值已经得到,时间复杂度 , 是每个强联通分量,由于 ,就能过惹。
代码
有自环和重边,所以建高消矩阵的时候要注意不能直接赋值!
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
#include <stack>
#include <queue>
const int eps = 1e-8;
const int MAXD = 100;
const int MAXN = 10000;
const int MAXM = 1000000;
struct Edge {
int v, nxt;
}E[MAXM + 5];
int EdgeCnt, Adj[MAXN + 5];
void Init() {
EdgeCnt = 0;
memset(Adj, -1, sizeof Adj);
}
void AddEdge(int u, int v) {
E[++EdgeCnt] = (Edge){v, Adj[u]}, Adj[u] = EdgeCnt;
}
int SccCnt;
int Bel[MAXN + 5], ID[MAXN + 5];
std::vector<int> Scc[MAXN + 5];
std::stack<int> _;
int Dfn[MAXN + 5], Low[MAXN + 5], DfnCnt;
void Tarjan(int u) { // 找强联通分量
_.push(u);
Low[u] = Dfn[u] = ++DfnCnt;
for (int i = Adj[u]; ~i; i = E[i].nxt) {
int v = E[i].v;
if (!Dfn[v]) {
Tarjan(v);
Low[u] = std::min(Low[u], Low[v]);
}
else if (!Bel[v])
Low[u] = std::min(Low[u], Dfn[v]);
}
if (Low[u] == Dfn[u]) {
++SccCnt;
int v, cnt = 0;
do {
Bel[v = _.top()] = SccCnt;
ID[v] = ++cnt;
Scc[SccCnt].push_back(v);
_.pop();
} while (v != u);
}
}
int In[MAXN + 5];
void TopSort(std::vector<int> &res) { // 对每个强联通分量拓扑排序
for (int i = 1; i <= SccCnt; i++)
for (int j = 0; j < int(Scc[i].size()); j++) {
int u = Scc[i][j];
for (int k = Adj[u]; ~k; k = E[k].nxt)
if (Bel[E[k].v] != i)
In[Bel[E[k].v]]++;
}
std::queue<int> Q;
for (int i = 1; i <= SccCnt; i++)
if (!In[i])
Q.push(i);
while (!Q.empty()) {
int u = Q.front(); Q.pop();
res.push_back(u);
for (int i = 0; i < int(Scc[u].size()); i++) {
int x = Scc[u][i];
for (int k = Adj[x]; ~k; k = E[k].nxt) {
int v = Bel[E[k].v];
In[v]--;
if (!In[v])
Q.push(v);
}
}
}
}
int Out[MAXN + 5];
std::vector<int> P;
double A[MAXD + 5][MAXD + 5], Dp[MAXN + 5];
double Abs(int x) {
return x < 0 ? -x : x;
}
int Cmp(double x, double y) {
return (Abs(x - y) > eps) * ((x - y > 0) ? 1 : -1);
}
double Res[MAXD + 5];
void Guass(int N) {
for (int i = 1; i <= N; i++) {
int pos = i;
for (int j = i + 1; j <= N; j++)
if (Cmp(A[j][i], A[pos][i]) == 1)
pos = i;
for (int j = i; j <= N + 1; j++)
std::swap(A[i][j], A[pos][j]);
double tmp = A[i][i];
for (int j = i; j <= N + 1; j++)
A[i][j] /= tmp;
for (int j = i + 1; j <= N; j++) {
tmp = A[j][i];
for (int k = i; k <= N + 1; k++)
A[j][k] -= A[i][k] * tmp;
}
}
for (int i = N; i >= 1; i--) {
Res[i] = A[i][N + 1];
for (int j = i + 1; j <= N; j++)
Res[i] -= Res[j] * A[i][j];
}
}
int main() {
Init();
int N, M, S, T;
scanf("%d%d%d%d", &N, &M, &S, &T);
for (int i = 1; i <= M; i++) {
int u, v; scanf("%d%d", &u, &v);
Out[u]++; AddEdge(u, v);
}
Tarjan(S);
for (int i = 1; i <= N; i++)
if (i != T && Dfn[i] && !Out[i])
return puts("INF"), 0;
if (!Dfn[T])
return puts("INF"), 0;
TopSort(P);
for (int i = P.size() - 1; i >= 0; i--) { // 按照拓扑倒序对每个强连通分量高消
int c = P[i];
memset(A, 0, sizeof A);
for (int j = 0; j < int(Scc[c].size()); j++) {
int u = Scc[c][j];
A[ID[u]][ID[u]] = 1.0;
A[ID[u]][Scc[c].size() + 1] = u != T;
if (u == T) // 随机游走的问题都要注意终点 continue,因为走到终点就停下来了
continue;
for (int k = Adj[u]; ~k; k = E[k].nxt) {
int v = E[k].v;
if (Bel[v] == c) // 同一个强连通分量的点
A[ID[u]][ID[v]] += -1.0 / Out[u];
else // 不在同一个强连通分量的点如果访问到了,就肯定在拓扑序的后面,因此它的 Dp 值一定已经算出来了
A[ID[u]][Scc[c].size() + 1] += Dp[v] / Out[u];
// 都是 += 因为有自环
}
}
Guass(Scc[c].size());
memset(A, 0, sizeof A);
for (int j = 0; j < int(Scc[c].size()); j++) {
int u = Scc[c][j];
Dp[u] = Res[ID[u]];
}
}
printf("%.3f", Dp[S]);
return 0;
}