擴展Dijkstra


本文從另一個角度理解Dijkstra算法,可能會與通常Dijkstra算法的講解有一些區別。

 

最短路問題:給定有向圖$G = (V, E)$,每條邊形如$(x, y, w)$,其中$w$表示節點$x$至節點$y$的距離為$w \geq 0$。求節點$s$至節點$t$的最短路徑長度。

Dijkstra算法:令$f(x)$表示節點$x$至節點$t$的最短路徑長度。則

$$
f(x) = \min_{(x, y, w) \in E} f(y)+w,
$$

邊界條件為$f(t) = 0$。

原理:我們可以通過迭代的方式計算出$f(x)$。初始狀態取$f(x) = \infty (x \neq t)$以及$f(t) = 0$。迭代停止的條件為一次迭代后所有$f(x)$的值未發生變化。在迭代$n = |V|$次之后必然收斂。於是,我們可以得到一個$O(n(n+m))$的迭代算法,其中$m = |E|$。

優化的方式就是用一個堆模擬迭代。這個堆存放所有$f(y)$的值,每次取出最小的$f(y)$,那么這個$f(y)$在今后的迭代中不再變化,從而由它影響到的節點$x$必須滿足$(x, y, w) \in E$,只需擴展$y$的所有反向邊即可。因為每個節點只會從堆中取出一次,從而至多$O(n+m)$個值會因擴展而插入堆中,故復雜度為$O((n+m)\log (n+m))$。

注:使用Fibonacci堆可做到$O(m+n\log n)$。

 

CodeForces 1407E. Egor in the Republic of Dagestan

給定$n$個節點,$m$條邊的有向圖$G = (V, E)$,每條有向邊形如$(x, y, c)$其中$c \in \{0, 1\}$。

要求給出一個節點染色方案$a_1, a_2, \dots, a_n \in \{0, 1\}$,使得對每個點$1 \leq x \leq n$,僅保留形如$(x, y, a_u)$的邊后,節點$1$至節點$n$的最短路最大。$n, m \leq 10^5$。

解法:令$f(x)$表示節點$x$至節點$n$的最短路的最大可能值。則

$$
f(x) = \max_{c \in \{0, 1\}} \min_{(x, y, c) \in E} f(y)+1,
$$

邊界條件為$f(n) = 0$。

初始狀態為$f(x) = \infty (x \neq n)$以及$f(n) = 0$,迭代以上式子$n$次可收斂。從而得到一個$O(n(n+m))$的算法。

優化方式也是每次取出最小的$f(y)$來擴展即可,進而復雜度為$O((n+m)\log(n+m))$。由於這題邊權為$1$,只需隊列(BFS)而不需要堆即可取出最小值,故復雜度可做到$O(n+m)$。

參考代碼:code

 

KickStart 2020 Round E. Golden Stone

給定$N$個節點,$M$條邊的無向圖$G = (V, E)$,一共有$S$種寶石,以及每個節點上擁有的寶石情況(擁有意味着無限資源)。

有$R$個打造方式,每個打造方式形如$(a_1, a_2, \dots, a_k) \to b$,其中$k \leq 3$,表示在同一個節點若集齊寶石$a_1, a_2, \dots, a_k$各一顆,可將他們打造成寶石$b$。

你可以以$1$單位花費,將一顆寶石從一個節點經過一條邊運送到另一個節點。

求打造寶石$1$的最小花費。$N, M, S, R \leq 500$。

解法:令$f(x, c)$表示在節點$x$得到寶石$c$的最小花費。則

$$
f(x, c) = \min \begin{cases}
    f(y, c) + 1 & (x, y) \in E \\
    \sum_{i=1}^k f(x, a_i) & (a_1, a_2, \dots, a_k) \to b \land c \in \{a_i\}_{i=1}^k
\end{cases},
$$

邊界條件為,$f(x, c) = 0$若節點$x$擁有寶石$c$。

初始狀態為若節點$x$擁有寶石$c$則$f(x, c) = 0$否則$f(x, c) = \infty$,迭代以上式子$NS$次可收斂。從而得到一個$O((NS)^2(M+R))$的算法。

優化方式每次取出最小的$f(y, c)$擴展即可,時間復雜度為$O((NS+MS+NR)\log(NS+MS+NR))$。

參考代碼:

void solve()
{
	int n, m, s, r;
	read(n, m, s, r);
	auto v = arr<vector<int>>(n + 1);
	for (int i = 1; i <= m; ++i)
	{
		int x, y;
		read(x, y);
		v[x].push_back(y);
		v[y].push_back(x);
	}
	auto has = arr<vector<int>>(n + 1);
	for (int i = 1; i <= n; ++i)
	{
		int k;
		read(k);
		vector<int> c(k);
		input(c);
		has[i] = c;
	}
	auto to = arr<vector<pair<int, vector<int>>>>(s + 1);
	for (int i = 1; i <= r; ++i)
	{
		int k;
		read(k);
		vector<int> a(k);
		input(a);
		int b;
		read(b);
		for (int x = 0; x < k; ++x)
		{
			vector<int> tmp;
			for (int y = 0; y < k; ++y) if (x != y) tmp.push_back(a[y]);
			to[a[x]].push_back({ b, tmp });
		}
	}

	const ll INF = 1000000000000LL;
	auto f = arr<ll>(n + 1, s + 1);
	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= s; ++j)
			f[i][j] = INF;
	struct node
	{
		ll val;
		int pos, color;
		bool operator < (const node& rhs) const
		{
			return val > rhs.val;
		}
	};
	priority_queue<node> q;
	for (int i = 1; i <= n; ++i)
	{
		for (auto c : has[i]) q.push({ 0, i, c });
	}
	while (!q.empty())
	{
		auto cur = q.top();
		q.pop();
		if (freshmin(f[cur.pos][cur.color], cur.val))
		{
			for (auto nxt : v[cur.pos])
			{
				if (cur.val + 1 < INF) q.push({ cur.val + 1, nxt, cur.color });
			}
			for (auto& e : to[cur.color])
			{
				const int& b = e.first;
				const vector<int>& a = e.second;
				ll tmp = cur.val;
				for (auto c : a) tmp += f[cur.pos][c];
				if (tmp < INF) q.push({ tmp, cur.pos, b });
			}
		}
	}
	ll res = INF;
	for (int i = 1; i <= n; ++i) freshmin(res, f[i][1]);
	if (res == INF) res = -1;
	writeln(res);
}

  

 

CodeForces 1387C. Viruses

給你一個上下文無關文法$G = (V, \Sigma, R)$,其中

  • $V = \{2, \dots, n-1\}$是非終結符的集合;
  • $\Sigma = \{0, 1\}$是終結符的集合;
  • $R \subseteq V \times (V \cup \Sigma)^+$是產生式的集合。

特別地,這個上下文無關文法不會產生空串,即$\epsilon \notin L(G)$。

同時給出$m$個字符串$s_1, s_2, \dots, s_m \in \{0, 1\}^+$。若字符串$s$是$t$的子串,我們記作$s \models t$。

對$2 \leq S < n$,令$G_S = (V, \Sigma, R, S)$表示$S$為初始符號的上下文無關文法,

  • 找到一個字符串$t \in L(G_S)$,使得對所有$1 \leq i \leq m$,$s_i \not\models t$;
  • 或者回答不存在這樣的字符串。

約束:$|R| \leq 100, |s| = \sum_{i=1}^m |s_i| \leq 50$。其中,一個產生式$r$形如

$$r: a \to b_1 b_2 \dots b_k,$$

並記$|r| = k$。我們定義$|R| = \sum_{r \in R} |r|$。

解法:首先,我們將上下文無關文法$G$化為Chomsky范式,即每個轉移$r$的長度$|r| \leq 2$。方法為,考慮轉移$r: a \to b_1 b_2 \dots b_k$,其中$k \geq 3$,我們定義

$$
\begin{aligned}
a & \to b_1 c_1, \\
c_1 & \to b_2 c_2, \\
c_2 & \to b_3 c_3, \\
\dots \\
c_{k-3} & \to b_{k-2} c_{k-2}, \\
c_{k-2} & \to b_{k-1} b_k.
\end{aligned}
$$

這樣轉化后,轉移式的規模仍為$O(|R|)$。以下則直接假設$|r| \in \{1, 2\}$。

接着,建立$m$個字符串$s_1, s_2, \dots, s_m$的AC自動機,則該自動機的狀態集合為$\Gamma$,$|\Gamma| = O(|s|)$,並記$\downarrow$為其初始狀態,$\delta: \Gamma \times \Sigma \to \Gamma$表示其轉移函數。令$f(S, x, y)$表示符號$S$所能產生的所有字符串$L(G_S)$中的長度最小的字符串$t$的長度,使得$\delta(x, t) = y$且不經過接受狀態。則我們要求$f(S, \downarrow, y)$,其中$y$不為接受狀態。我們有

$$
f(S, x, y) = \min \begin{cases}
\min_u \{ f(b_1, x, u) + f(b_2, u, y) & S \to b_1b_2 \\
f(b_1, x, y) & S \to b_1
\end{cases},
$$

邊界條件為對$a \in \{0, 1\}$,$f(a, x, y) = 1$若$\delta(x, a) = y$且$x$與$y$均不為接受狀態。

狀態數一共$O(|R||s|^2)$個,每輪迭代復雜度為$O(|R||s|^3)$,從而總復雜度為$O(|R|^2|s|^5)$,是不可接受的。

但我們仍可用堆優化,每次取出最小的$f(S, x, y)$擴展即可,而每個狀態均攤擴展$O(|s|)$個新狀態,從而復雜度為$O(|R||s|^3 \log(|R||s|^3))$。

代碼:code

注:若限制堆中每個狀態至多有$1$個最優擴展,則堆中至多有$O(|R||s|^2)$個元素,復雜度可降為$O(|R||s|^3 \log(|R||s|^2))$。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM