[NOIP2017 提高組] 逛公園 題解
阿新 • • 發佈:2021-08-30
- \(\text{30pts}\)
顯然就是這道題。
- \(\text{100pts}\)
肯定要跑最短路的。令 \(d_i\) 表示 \(i\) 到 \(n\) 的最短路長度。
\(f_{u,i}\) 表示從 \(u\) 到 \(n\) 長度為 \(d_u+i\) 的路徑個數。
\(dis\) 陣列顯然可以建反圖然後跑源點為 \(n\) 的最短路。
考慮 \(f\) 陣列的轉移。
\[f_{u,i}=\sum{f_{v,i-(d_v+w-d_u)}} \]其中,存在 \(u\) 到 \(v\) 的邊且邊權為 \(w\)。
可以這樣做的原因就是 \(d_v+w-d_u\) 就是給 \(v\)
這樣可以直接記憶化搜尋也可以就跑一遍拓撲排序,我是用記搜寫的。
有無數種的路線的情況就是有邊權均為 \(0\) 的環,在記搜裡面記錄個 \(exi_{u,i}\) 陣列即可,如果此時陣列值為真的話就相當於找到了邊權為 \(0\) 的環。
顯然時間複雜度 \(O(m\log m+nk)\)。
程式碼:
#include <cstdio> #include <cstring> #include <iostream> #include <vector> #include <queue> using namespace std; using LL = long long; const int INF = 0x3f3f3f3f; const int N = 1e5 + 5; struct Edge { int to, w; Edge () {} Edge (int _to, int _w) { to = _to, w = _w; } ~Edge () {} }; struct Node { int id, dis; Node () {} Node (int _id, int _dis) { id = _id, dis = _dis; } ~Node () {} friend bool operator < (const Node& n1, const Node& n2) { return n1.dis > n2.dis; } }; vector <Edge> G[N], rG[N]; priority_queue <Node> q; int T, n, m, k, p; int u, v, w; LL f[N][55], dis[N]; bool flag, vis[N], exi[N][55]; int add (int x, int y) { return (x + y >= p) ? (x + y - p) : (x + y); } void init () { flag = false; for (int i = 1; i <= n; ++i) { G[i].clear(), rG[i].clear(); for (int j = 0; j <= k; ++j) f[i][j] = -1; } while (!q.empty()) q.pop(); } void AddEdge (int u, int v, int w) { G[u].push_back(Edge(v, w)); rG[v].push_back(Edge(u, w)); } void Dijkstra (int s) { fill(dis + 1, dis + 1 + n, INF), fill(vis + 1, vis + 1 + n, false); q.push(Node(s, 0)), dis[s] = 0; while (!q.empty()) { Node nowtop = q.top(); q.pop(); int nowid = nowtop.id; if (vis[nowid]) continue ; vis[nowid] = true; for (auto Ed : rG[nowid]) { int to = Ed.to, w = Ed.w; if (dis[to] > dis[nowid] + w) { dis[to] = dis[nowid] + w; q.push(Node(to, dis[to])); } } } } LL Solve (int x, int y) { if (exi[x][y]) { flag = true; return 0; } if (f[x][y] > 0) return f[x][y]; // f[x][y] = 0; exi[x][y] = true; LL ret = 0; for (auto Ed : G[x]) { int to = Ed.to, w = Ed.w; int temp = y - (dis[to] + w - dis[x]); if (temp < 0 || temp > k) continue ; ret = add(ret, Solve(to, temp)); if (flag) return 0; } if (x == n && !y) ret = 1; exi[x][y] = false; return f[x][y] = ret; } int main() { scanf("%d", &T); while (T--) { init(); scanf("%d%d%d%d", &n, &m, &k, &p); for (int i = 1; i <= m; ++i) { scanf("%d%d%d", &u, &v, &w); AddEdge(u, v, w); } Dijkstra(n); LL res = 0; for (int i = 0; i <= k; ++i) { for (int j = 1; j <= n; ++j) for (int s = 0; s <= k; ++s) exi[j][s] = false; res = add(res, Solve(1, i)); } printf("%lld\n", flag ? -1 : res); } return 0; }
貌似不開 O2 過不了(