1. 程式人生 > 其它 >第1屆ICPC青少年程式設計競賽 G.Dynamic Graph

第1屆ICPC青少年程式設計競賽 G.Dynamic Graph

題目描述

給定一張 \(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;
}