CF938G Shortest Path Queries
Shortest Path Queries
题面翻译
给出一个连通带权无向图,边有边权,要求支持 \(q\) 个操作:
\(1\) \(x\) \(y\) \(d\) 在原图中加入一条 \(x\) 到 \(y\) 权值为 \(d\) 的边
\(2\) \(x\) \(y\) 把图中 \(x\) 到 \(y\) 的边删掉
\(3\) \(x\) \(y\) 表示询问 \(x\) 到 \(y\) 的异或最短路
保证任意操作后当前状态下的图连通无重边自环且操作均合法
\(1 \leq n,m,q\le200000\)。
Solution
先不考虑有加删边的情况,只考虑操作 \(3\) 怎么去计算答案。
由于图是一个连通无向图,因此每一个环对于每个点都是可以到达的。考虑找出一条 \(x\) 到 \(y\) 的链,然后会发现任何一个环都是可以拼到这条链上的。具体做法就是从链上一个点出发到达这个环,然后走完整个环后原路返回,这样由于异或的性质从链上点到环的这条路径将不会对答案产生贡献。同时会发现,无论选择任何链作为初始的链计算出的答案都相同。证明的话考虑将两条 \(x\to y\) 的路径拼起来看作一个环,那么选和不选这个环就代表了选择这两条链中的其中一个的情况。
那么问题就是找到一个可行的路径,然后再使用图上所有的环来对这个路径进行增广。环之间的贡献是异或,因此可以使用线性基来找出异或最短路。
其实这就是 [WC2011] 最大XOR和路径 了。所以你可以先去把这道题写完了再来写这道题。
考虑有了加删边操作过后怎么处理。删边显然不好维护,所以使用线段树分治来处理删边的情况。
考虑加边怎么处理。找环显然可以用并查集来找,那么只需要快速的算两点之间的异或和就行了。由于异或的性质,我们可以维护每个点到当前集合根节点路径上的异或和 \(v\),这样只需要两点的 \(v\) 值异或起来就可以得到两点路径的异或和。并查集用按秩合并就行,时间复杂度 \(\mathcal O(n\log^2 n)\)。
Code
// Cirno is not baka!
#include <bits/stdc++.h>
using namespace std;
#ifdef CIRNO
#include <debug.hpp>
#else
#define Debug(...)
#endif
#define For(i, a, b) for (int i = (a); i <= (int)(b); ++i)
#define Rof(i, a, b) for (int i = (a); i >= (int)(b); --i)
#define All(x) x.begin(), x.end()
#define pii pair<int, int>
#define fi first
#define se second
#define i64 long long
#define mkp make_pair
// #define int long long
#define epb emplace_back
const int _N = 2e5 + 5, mod = 1e9 + 7, inf = 1e9;
template<typename T> void Max(T &x, T y) {x = max(x, y);}
template<typename T> void Min(T &x, T y) {x = min(x, y);}
namespace BakaCirno {
struct LinearBasis {
int val[32];
LinearBasis() {memset(val, 0, sizeof val);}
void Insert(int x) {
Rof(i, 30, 0) if (x & (1 << i))
if (val[i]) x ^= val[i];
else {val[i] = x; break;}
}
int Query(int x) {
Rof(i, 30, 0) if ((x ^ val[i]) < x)
x ^= val[i];
return x;
}
};
struct Edge {
int x, y, w;
Edge(int x = 0, int y = 0, int w = 0)
: x(x), y(y), w(w) {}
};
int N, M, Q;
map<pii, pii> mp;
pii qu[_N];
vector<Edge> vec[_N << 2];
#define LC (k << 1)
#define RC (k << 1 | 1)
#define mid ((l + r) >> 1)
void Add(int k, int l, int r, int a, int b, Edge v) {
if (l >= a && r <= b) return vec[k].epb(v), void();
if (a <= mid) Add(LC, l, mid, a, b, v);
if (b > mid) Add(RC, mid + 1, r, a, b, v);
}
int fa[_N], dep[_N], dis[_N];
int Find(int x) {return x == fa[x] ? x : Find(fa[x]);}
int Dis(int x) {return x == fa[x] ? 0 : (dis[x] ^ Dis(fa[x]));}
void Solve(int k, int l, int r, LinearBasis lb) {
stack<tuple<int, int, bool>> stk;
for (auto [x, y, w]: vec[k]) {
int fx = Find(x), fy = Find(y);
w ^= Dis(x) ^ Dis(y);
if (fx == fy) lb.Insert(w);
else {
if (dep[fx] > dep[fy]) swap(x, y), swap(fx, fy);
stk.emplace(fx, fy, dep[fx] == dep[fy]);
fa[fx] = fy, dis[fx] = w;
dep[fy] += dep[fx] == dep[fy];
}
}
if (l == r) {
if (qu[l].fi)
cout << lb.Query(Dis(qu[l].fi) ^ Dis(qu[l].se)) << '\n';
} else
Solve(LC, l, mid, lb), Solve(RC, mid + 1, r, lb);
while (!stk.empty()) {
auto [x, y, w] = stk.top(); stk.pop();
fa[x] = x, dis[x] = 0;
dep[y] -= w;
}
}
void _() {
cin >> N >> M;
For(i, 1, M) {
int x, y, w; cin >> x >> y >> w;
if (x > y) swap(x, y);
mp[mkp(x, y)] = mkp(w, 0);
}
cin >> Q;
For(i, 1, Q) {
int opt, x, y, d;
cin >> opt >> x >> y;
if (x > y) swap(x, y);
if (opt == 1) {
cin >> d;
mp[mkp(x, y)] = mkp(d, i);
} else if (opt == 2) {
pii tmp = mp[mkp(x, y)];
// Debug(tmp.se, i - 1, x, y, tmp.fi, '\n');
Add(1, 0, Q, tmp.se, i - 1, Edge(x, y, tmp.fi));
mp.erase(mkp(x, y));
} else {
qu[i] = mkp(x, y);
// Debug(x, y, '\n');
}
}
for (auto pr: mp) {
// Debug(pr.se.se, Q, pr.fi.fi, pr.fi.se, pr.se.fi, '\n');
Add(1, 0, Q, pr.se.se, Q, Edge(pr.fi.fi, pr.fi.se, pr.se.fi));
}
iota(fa + 1, fa + N + 1, 1);
Solve(1, 0, Q, LinearBasis());
}
}
void File(const string file) {
freopen((file + ".in").c_str(), "r", stdin);
freopen((file + ".out").c_str(), "w", stdout);
}
signed main() {
// File("t");
cin.tie(0)->sync_with_stdio(0); int T = 1;
// cin >> T;
while (T--) BakaCirno::_();
}