「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]\)
於是問題被完美的解決了。複雜度 \(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;
}