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

文章目錄

題面

洛谷P6030 [SDOI2012]走迷宮

分析

dp[u]dp[u]表示從uu走到TT的期望步數,VuV_u表示uu能到的點的集合,那麼有dp[u]=vVu1Vu(dp[v]+1)=1Vuvvudp[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}理解這個式子,就考慮從uuvv1Vu\dfrac{1}{|V_u|}的概率,並且若從uu到了vv,再到TT,步數就會多11

對於每個點得到這個式子,正常情況下我們直接高斯消元解得每個dp[u]dp[u],但是這裏n104n\leq10^4,高斯消元O(n3)O(n^3),GG。於是我們觀察到保證強連通分量的大小不超過100\boldsymbol{100}這個條件,由於強聯通分量外的dpdp值與強連通分量內的dpdp值沒有相互包含關係,只有單方向的遞推關係,所以我們按照拓撲序倒着對每個強連通分量進行高斯消元,就能保證對當前強連通分量消元時,所有相關的其他dpdp值已經得到,時間複雜度O(S3)O(\sum|S|^3)SS是每個強聯通分量,由於S3<SS2=nS2\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;
}
發佈了185 篇原創文章 · 獲贊 531 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章