[SDOI2012]走迷宫(Tarjan + 概率DP + 高斯消元)

文章目录

题面

洛谷P6030 [SDOI2012]走迷宫

分析

d p [ u ] dp[u] 表示从 u u 走到 T T 的期望步数, V u V_u 表示 u u 能到的点的集合,那么有 d p [ u ] = v V u 1 V u ( d p [ v ] + 1 ) = 1 V u v v u d p [ v ] + 1 \begin{aligned} dp[u]&=\sum\limits_{v\in V_u}\dfrac{1}{|V_u|}\cdot\left(dp[v] + 1\right)\\ &=\dfrac{1}{|V_u|}\sum\limits_{v\in v_u}dp[v] + 1 \end{aligned} 理解这个式子,就考虑从 u u v v 1 V u \dfrac{1}{|V_u|} 的概率,并且若从 u u 到了 v v ,再到 T T ,步数就会多 1 1

对于每个点得到这个式子,正常情况下我们直接高斯消元解得每个 d p [ u ] dp[u] ,但是这里 n 1 0 4 n\leq10^4 ,高斯消元 O ( n 3 ) O(n^3) ,GG。于是我们观察到保证强连通分量的大小不超过 100 \boldsymbol{100} 这个条件,由于强联通分量外的 d p dp 值与强连通分量内的 d p dp 值没有相互包含关系,只有单方向的递推关系,所以我们按照拓扑序倒着对每个强连通分量进行高斯消元,就能保证对当前强连通分量消元时,所有相关的其他 d p dp 值已经得到,时间复杂度 O ( S 3 ) O(\sum|S|^3) S S 是每个强联通分量,由于 S 3 < S S 2 = n S 2 \sum|S|^3<\sum|S|\cdot|S|^2=n\cdot|S|^2 ,就能过惹。

代码

有自环和重边,所以建高消矩阵的时候要注意不能直接赋值!

#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;
}

猜你喜欢

转载自blog.csdn.net/C20190102/article/details/104233555