CF1163F Indecisive Taxi Fee 题解
本文变量和题目中的一致。时间复杂度
解题思路
首先基本的思路是,最短路显然是一定不经过
前者是删边最短路。删边最短路的基本做法和证明可以参考《图论基础》- Alex_Wei 的 1.5.2 节。
后者更容易。设
复杂度为
优化复杂度
但是我们还有优化空间!我们将挑战更稠密的图。
观察复杂度瓶颈,存在两个:求解最短路是
求解最短路这一步前人已经优化过了:使用斐波那契堆优化 Dijkstra,即可得到 __gnu_pbds::priority_queue,采用 __gnu_pbds::thin_heap_tag 来做到相同的单源最短路复杂度。
现在问题是怎么样加速区间最值操作。由于是预处理,一定有区间取
void push_operation(int l, int r, int v)
{
int len = 31 ^ __builtin_clz(r - l + 1);
f[len][l] = min(f[len][l], v);
f[len][r - (1 << len) + 1] = min(f[len][r - (1 << len) + 1], v);
}
这个过程显然是
将所有最初的询问操作 push 完之后,从高位到低位下放
这样整个程序的时间复杂度就是
由于整个过程是离线的,考虑类似我写的这篇博客的做法。这样空间复杂度就可以压至
代码实现
代码不是很优美,所以我加了点注释。应该能看懂吧:
#include <iostream>
#include <cstdio>
#include <cctype>
#include <vector>
#include <cstring>
#include <algorithm>
#include <bitset>
#include <ext/pb_ds/priority_queue.hpp>
using namespace std;
#define LL long long
const int N = 200005, M = 200005;
const LL INF64 = 1e18;
struct Edge { int v, w, nxt; };
int tot = 1, h[N];
Edge e[M << 1];
void add_edge(int u, int v, int w)
{
e[++tot] = {v, w, h[u]}; h[u] = tot;
}
#define rep(x, i) for (int i = h[x]; i; i = e[i].nxt)
// 优先队列优化的 Dijkstra 单源最短路算法
class Dijkstra
{
public:
static void SSSP(int n, int s, LL *d, int *f, int *fid)
{
for (int i = 1; i <= n; ++i) d[i] = INF64;
for (int i = 1; i <= n; ++i) it[i] = nullptr;
d[s] = 0;
Q.push({s, 0});
Node x; LL w;
while (!Q.empty()) {
x = Q.top();
Q.pop();
rep(x.id, i) {
w = x.val + e[i].w;
if (w < d[e[i].v]) {
d[e[i].v] = w, f[e[i].v] = x.id, fid[e[i].v] = i >> 1;
if (it[e[i].v] == nullptr) it[e[i].v] = Q.push({e[i].v, w});
else Q.modify(it[e[i].v], Node{e[i].v, w});
}
}
}
}
private:
struct Node
{
int id;
LL val;
bool operator<(const Node& t) const { return val > t.val; }
};
// 这里为了常数使用了 __gnu_pbds::pairing_heap_tag,也就是默认 tag。
// 实际上若需要达到 O(n lg n + m) 的复杂度应该使用 __gnu_pbds::thin_heap_tag。
using tag = __gnu_pbds::pairing_heap_tag;
using heap = __gnu_pbds::priority_queue<Node, less<>, tag>;
static heap Q;
static heap::point_iterator it[N];
};
Dijkstra::heap Dijkstra::Q;
Dijkstra::heap::point_iterator Dijkstra::it[N];
int n, m;
vector<int> SP; ///< 最短路上的节点
LL d1[N], dn[N];
int f1[N], fn[N];
int gtf(int *f, int x) { while (f[x] != x) x = f[x] = f[f[x]]; return x; }
int id[N], fid[N];
bitset<M + 3> on_path;
int val[N], q;
LL ans_del[N]; ///< 删边最短路的答案
vector<pair<int, LL> > qr[19]; ///< qr[i] 储存了所有长度为 2^i 的区间操作。
template<typename T>
void tmn(T &x, T y) { if (x > y) x = y; }
// 处理区间取 min 操作。
void push_operation(int l, int r, LL v)
{
if (l > r) return ;
int len = 31 ^ __builtin_clz(r - l + 1);
qr[len].emplace_back(l, v);
qr[len].emplace_back(r - (1 << len) + 1, v);
}
// 预处理删边最短路。
void solve_del_edge()
{
Dijkstra::SSSP(n, 1, d1, f1, fid);
for (int i = n; i != 1; i = f1[i]) SP.push_back(i);
SP.push_back(1);
reverse(SP.begin(), SP.end());
q = SP.size();
for (int i = 0; i < q; ++i) {
id[SP[i]] = i + 1;
on_path.set(fid[SP[i]]);
}
for (int i : SP) f1[i] = i;
for (int i = 1; i <= n; i++) f1[i] = gtf(f1, i);
Dijkstra::SSSP(n, n, dn, fn, fid);
for (int i : SP) fn[i] = i;
for (int i = 1; i <= n; i++) fn[i] = gtf(fn, i);
for (int i = 1; i <= m; ++i) if (!on_path[i]) {
int v = e[i * 2].v, u = e[i * 2 + 1].v, w = e[i * 2].w;
if (id[f1[u]] > id[fn[v]]) swap(u, v);
push_operation(id[f1[u]] + 1, id[fn[v]],
min(d1[u] + dn[v], d1[v] + dn[u]) + w);
}
memset(ans_del, 0x3f, sizeof(ans_del));
// O(n lg n + m) 离线处理答案。
for (int i = __lg(q); i >= 1; --i) {
for (auto j : qr[i]) tmn(ans_del[j.first], j.second);
int l = 1 << (i - 1);
for (int j = q - (1 << i) + 1; j >= 1; --j) {
tmn(ans_del[j + l], ans_del[j]);
}
}
for (auto j : qr[0]) tmn(ans_del[j.first], j.second);
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0), cout.tie(0);
int qq;
cin >> n >> m >> qq;
for (int i = 1, u, v, w; i <= m; ++i) {
cin >> u >> v >> w;
add_edge(u, v, w);
add_edge(v, u, w);
}
solve_del_edge();
int t, x;
while (qq--) {
cin >> t >> x;
LL del, raw;
if (on_path[t]) {
del = ans_del[max(id[e[t * 2].v], id[e[t * 2 + 1].v])];
raw = d1[n] - e[t * 2].w + x;
}
else {
int v = e[t * 2].v, u = e[t * 2 + 1].v;
del = d1[n];
raw = min(d1[u] + x + dn[v], d1[v] + x + dn[u]);
}
cout << min(del, raw) << '\n';
}
return 0;
}
写在最后
尽管理论复杂度确实更优了,在 thin_heap 的超大常数加持下似乎并没有太大优势。在相同效率的读入下,