给定一张 \(n\) 个点的无向图,刚开始为空。执行 \(m\) 次操作 \((3\) 种操作\()\)。
1 u v w
,在点 \(u\) 与点 \(v\) 之间加入一条权值为 \(w\) 的边。2 id
,删除第 \(id\) 次操作加入的边。3 u v w
,询问点 \(u\) 与点 \(v\) 之间是否存在一条权值模 \(F\) 为 \(w\) 的路径 \((\)不一定要为简单路径\()\)。\(1\le n,m\le 10^5,1\le F\le 10^9\)。
显然可以按时间分治建立线段树,可以参考 线段树分治模板。
考虑如何判断模 \(F\) 为 \(w\) 的路径。
如果询问的 \(x,y\) 不在同一个连通块内则必然无解。否则 \(x,y\) 之间的路径权值和必定可以表示为 \(L+k\cdot G(\text{mod }F)\),\(L\) 表示 \(x,y\) 之间固定的一条路径权值和,\(G\) 表示 \(x,y\) 所在连通块的所有环的长度和 \(F\) 的最大公约数。
若能证明任意一条路径权值和能表示成固定的一条路径权值和 \(L\) 和若干倍环长最大公约数 \(G\),就可以轻松维护。
考虑两条 \(x,y\) 之间不同的路径的对称差中,一定每个点的度数都是偶数,所以可以将其拆分成若干个环。那么就可以通过环来使得任意两种不同的路径相互转化。
所以就只需要维护环长最大公约数以及任意一条路径长度。
如果加入的边连接的是在同一连通块的 \(x,y\),则环长的最大公约数 \(G\) 更新为 \(\gcd(G,2w,w+L(x)+L(y))\)。其中 \(L(x)\) 表示 \(x\) 到并查集中 \(x\) 的根的距离。
否则环长的最大公约数 \(G\) 更新为 \(\gcd(G_x,G_y,2w)\),其中 \(G_x\) 表示 \(x\) 所在连通块的环长的最大公约数。
使用按秩合并的并查集以及线段树合并进行维护即可,时间复杂度 \(O(n\log n\log m)\)。
\(\color{blue}{\text{code}}\)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e5 + 5; struct Edge{ int x, y, w; } edge[N]; struct dsu{ int ty, x; ll w; } s[N * 36]; int n, m, F, top, op[N], fa[N], sz[N], del[N]; ll d[N], G[N]; vector <Edge> tree[N << 2]; inline ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; } inline int find(int x) { return fa[x] == x ? fa[x] : find(fa[x]); } inline ll D(int x) { ll ans = 0; while (fa[x] != x) ans += d[x], x = fa[x]; return ans; } inline void merge(int x, int y, int z) { int fx = find(x), fy = find(y); if (fx == fy) { s[++ top] = (dsu) {1, fx, G[fx]}; G[fx] = gcd(G[fx], D(x) + D(y) + z); G[fx] = gcd(G[fx], z + (x != y) * z); } // at the same block else { if (sz[fx] > sz[fy]) swap(fx, fy); s[++ top] = (dsu) {1, fy, G[fy]}; s[++ top] = (dsu) {2, fx, fy}; fa[fx] = fy, d[fx] = z - D(x) - D(y), sz[fy] += sz[fx]; G[fy] = gcd(G[fy], G[fx]); G[fy] = gcd(G[fy], z + z); } // at the different block } inline void split(int id) { dsu o = s[id]; if (o.ty == 1) G[o.x] = o.w; // delete an edge in a block else fa[o.x] = o.x, d[o.x] = 0, sz[o.w] -= sz[o.x]; // delete an edge to get two blocks } // delete idth operation inline void add(int x, int l, int r, int L, int R, Edge v) { if (L <= l && R >= r) return void(tree[x].emplace_back(v)); int mid = l + r >> 1; if (L <= mid) add(x << 1, l, mid, L, R, v); if (R > mid) add(x << 1 | 1, mid + 1, r, L, R, v); } inline void query(int id) { Edge pr = edge[id]; int fx = find(pr.x), fy = find(pr.y), ans = 0; if (fx == fy) { int g = gcd(G[fx], F); if (g) if ((pr.w - D(pr.x) - D(pr.y)) % g == 0) ans = 1; } cout << ans << '\n'; } inline void solve(int x, int l, int r) { int sz = top; for (Edge v : tree[x]) merge(v.x, v.y, v.w); if (l == r && op[r] == 3) query(r); if (l < r) { int mid = l + r >> 1; solve(x << 1, l, mid), solve(x << 1 | 1, mid + 1, r); } while (top > sz) split(top --); } int main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n >> m >> F; for (int i = 1; i <= n; ++ i) fa[i] = i, sz[i] = 1; for (int i = 1; i <= m; ++ i) { int type; cin >> type; op[i] = type; if (type == 1) { int x, y, w; cin >> x >> y >> w; edge[i] = (Edge) {x, y, w}; } else if (type == 2) { int id; cin >> id; add(1, 1, m, id, i - 1, edge[id]); del[id] = 1; } else { int x, y, w; cin >> x >> y >> w; edge[i] = (Edge) {x, y, w}; } } for (int i = 1; i <= m; ++ i) if (op[i] == 1 && !del[i]) add(1, 1, m, i, m, edge[i]); solve(1, 1, m); return 0; }