1. 程式人生 > 實用技巧 >「LOJ#2316」「NOIp2017」逛公園

「LOJ#2316」「NOIp2017」逛公園

Descripton

給定一個 \(n\) 個點,\(m\) 條邊的有向圖,第 \(i\) 條邊為 \((u_i, v_i, c_i)\)

\(1\)\(n\) 的最短路長為 \(d\),給定一個常數 \(k\),求 \(1\Rightarrow n\) 的總長不超過 \(d+k\) 的路徑個數。

答案對 \(p\) 取模,若有無窮條則輸出 \(-1\)

Hint

  • \(1\le n\le 10^5\)
  • \(1\le m\le 2\times 10^5\)
  • \(0\le k\le 50\)
  • \(1\le p\le 10^9\)
  • \(0\le c_i\le 10^9\)

Solution

首先對於 \(k=0\)

的部分分,就是一個 最短路計數,這是個做法顯然的經典問題。

我們嘗試將 \(k=0\) 拓展到 \(k \ge 0\) 的情況。為了簡化問題,我們設答案 \(\ne -1\)

\(k=0\) 一樣考慮一種 dp。定義 \(f(x, e)\) 表示走到頂點 \(x\),已經走了長度為 \(e\)冤枉路 的方案數。冤枉路指,當前路徑比最短路長度多出的長度。顯然,只有 \(e \in [0, k]\) 才有意義。我們注意到 \(k \le 50\),因此這樣設定狀態是可以的。

考慮如何轉移。設 \(x\) 的入邊為 \(\{(y_i, x, c_i)\}_{i=1}^k\),那麼:

\[f(x, e) = \left(\sum\limits_{i=1}^k f(y_i, e + (dist(x) - dist[y_i]) - c_i)\right) + [x=1][e=0] \]

這個式子的含義是,對於所有可以直接到達 \(x\) 的點 \(y_{i\sim k}\),我們嘗試 用最短路上的邊替換原來的這些邊 \(c_{1\sim k}\)。這樣會少走更多的冤枉路,即減少 \(e\)。最後那一項表示當到達起點並且剛好沒走冤枉路,那麼說明答案需要加一。

這整個東西可以通過記搜實現,答案即為 \(\sum_{i=0}^k f(n, i)\)

然而遺憾的是,我們沒有加入答案為 \(-1\) 的判斷。如果做到 \(f(x, e)\),發現之前已經做到過這裡,並且仍然在遞迴的棧中。這暗示著,我們發現了一個零環,這是可以繞無限圈的,說明答案就是 \(-1\) 了。

注意我們是暴力列舉每個 \(e\in[0, k]\)

計算的。如果發現任何一個 \(-1\),那麼答案直接是 \(-1\)

於是問題被完美的解決了。複雜度 \(O((n+m)k)\)

Code

/*
 * Author : _Wallace_
 * Source : https://www.cnblogs.com/-Wallace-/
 * Problem : LOJ#2316 NOIp2017 逛公園
 */
#include <cstring>
#include <iostream>
#include <queue>
#include <vector>

using namespace std;
const int N = 1e5 + 5;
const int K = 55;

int n, m, k, p;
struct edge {
	int to, len;
	edge(int x, int y) {
		to = x, len = y;
	}
}; vector<edge> G[N], R[N];
#define getEdge(x, y, g) for (vector<edge>::iterator y \
	= g[x].begin(); y != g[x].end(); y++)

struct heapNode {
	int pos, dis;
	heapNode(int p, int d) {
		pos = p, dis = d;
	}
	bool operator < (const heapNode& x) const {
		return dis > x.dis;
	}
}; priority_queue<heapNode> pq;
int dist[N];
bool book[N];

void Dijkstra() {
	memset(dist, 0x3f, sizeof(dist));
	memset(book, false, sizeof(book));
	while (!pq.empty()) pq.pop();
	
	pq.push(heapNode(1, dist[1] = 0));
	while (!pq.empty()) {
		int x = pq.top().pos;
		pq.pop();
		
		if (book[x]) continue;
		book[x] = true;
		
		getEdge(x, y, G)
			if (dist[y->to] > dist[x] + y->len) {
				dist[y->to] = dist[x] + y->len;
				pq.push(heapNode(y->to, dist[y->to]));
			}
	}
	return;
}

long long f[N][K];
bool vis[N][K];
long long dp(int x, int e) {
	if (e < 0|| e > k)
		return 0;
	if (vis[x][e]) {
		vis[x][e] = 0;
		return -1;
	}
	if (~f[x][e])
		return f[x][e];
	
	vis[x][e] = 1;
	long long& F = f[x][e] = 0;
	
	getEdge(x, y, R) {
		long long tmp = dp(y->to, e + (dist[x] - dist[y->to]) - y->len);
		if (!~tmp) {
			vis[x][e] = 0;
			return -1;
		}
		(F += tmp) %= p;
	}
	vis[x][e] = 0;
	
	if (x == 1 && e == 0)
		F++;
	return F %= p;
}

void solve() {
	cin >> n >> m >> k >> p;
	for (int i = 1; i <= n; i++)
		G[i].clear(), R[i].clear();
	for (int i = 1; i <= m; i++) {
		int u, v, w; cin >> u >> v >> w;
		G[u].push_back(edge(v, w));
		R[v].push_back(edge(u, w));
	}
	Dijkstra();
	
	memset(vis, 0, sizeof(vis));
	memset(f, -1, sizeof(f));
	
	long long ans = 0;
	for (int i = 0; i <= k; i++) {
		int tmp = dp(n, i);
		if (!~tmp) return cout << -1 << endl, void();
		(ans += tmp) %= p;
	}
	cout << ans << endl;
}

signed main() {
	ios::sync_with_stdio(false);
	int T; cin >> T;
	while (T--) solve();
	return 0;
}