主要说说这波卡常的感受.
求交费最多的一次的最小值.使用二分法.
即二分金额x,每次检查能否在忽略费用超过x的城市的情况下到达终点的最短路径长度是否小于血量b.由于没有负权边,采用Dijkstra.复杂度是O((n+m)logm log fmax),大概是107,勉强可以过.
实现起来没有什么难度,然而,被卡常了,本地第一个数据一直跑1.1s+.
那就只能走歪门斜路了,加个快读,开个O2,迫不得已又加个register,还是没能冲过去.STL常数是大,但我这么懒是不可能去学链式向前星的,太丑了.
最后发现在Dijkstra里有个地方可以剪枝:
while (!q.empty()) { E cur = q.top(); q.pop(); if (used[cur.to]) continue; used[cur.to] = true; dist[cur.to] = cur.wei; if (cur.to == n) break; // 加上这个剪枝 for (auto i : e[cur.to]) if (f[i.to] <= x) q.push({i.to, i.wei + cur.wei}); }
效果比卡常高到不知道哪去了.
#include <algorithm> #include <cstdio> #include <cstring> #include <iostream> #include <queue> #include <vector> using namespace std; struct E { int to; long long wei; bool operator<(const E &other) const { return wei > other.wei; } }; vector<E> e[10010]; int n, m, b, f[10010]; long long dist[10010]; bool used[10010]; inline int read() { char ch = getchar(); int x = 0, f = 1; while (ch > '9' || ch < '0') { if (ch == '-') f = -1; ch = getchar(); } while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); } return x * f; } bool check(int x) { // under x money fill(dist, dist + n + 1, 1e15); fill(used, used + n + 1, 0); priority_queue<E> q; if (f[1] <= x) q.push({1, 0}); while (!q.empty()) { E cur = q.top(); q.pop(); if (used[cur.to]) continue; used[cur.to] = true; dist[cur.to] = cur.wei; if (cur.to == n) break; for (auto i : e[cur.to]) if (f[i.to] <= x) q.push({i.to, i.wei + cur.wei}); } return dist[n] < b; } int main() { n = read(), m = read(), b = read(); int big = 0; for (int i = 1; i <= n; i++) f[i] = read(), big = max(big, f[i]); while (m--) { int x, y, w; x = read(), y = read(), w = read(); e[x].push_back({y, w}); e[y].push_back({x, w}); } int l = 0, r = big + 1; while (l < r) { int mid = l + r >> 1; if (check(mid)) r = mid; else l = mid + 1; } if (l != big + 1) printf("%d ", l); else puts("AFK"); return 0; }
所以如果只想知道确定起点与终点间的最短路,Dijkstra可以这样剪枝一下,效果显著.