AGC 031F Walk on Graph - 數論 - 並查集

題目傳送門

  傳送門

  考慮把這個過程倒過來,這樣每走一次就會變成 $2x + w$。

  樸素做法是判斷到某個點,值爲 $x$ 是否可行,考慮尋找一些性質來優化這個做法。

  不難發現直接做的話是單向邊,這樣處理起來比較困難。

  考慮一條邊 $(u, v, w)$,如果在這條邊上進行左右橫跳的話,可以從 $(u, x)$ 轉移到 $(v, 2x + w)$,即一個狀態有唯一後繼。同時因爲模數爲奇數,這一過程是可逆的,所以一個狀態有唯一前驅。因此這會形成一個環。我們不斷在這個環上走,可以從 $(v, 2x + w)$ 走到 $(u, x)$。因此可達性是雙向的。

  考慮一個點的某兩條出邊 $(a, b, w_1), (a, c, w_2)$,那麼可以得到狀態 $(a, 4x + 3w_1), (a, 4x + 3w_2)$,因此 $(a, x)$ 和 $(a, x + 3(w_1 - w_2))$ 是互相可達的。顯然這個連通塊中任意一個點 $p$ 都滿足 $(p, x)$ 和 $(p, x + 3(w_1 - w_2))$ 是互相可達的。

  考慮如果一對邊邊權的差爲 $d$,那麼我可以讓 $(p,x)$ 到達 $(p, x +3d)$ 。證明考慮從一條邊到另外一條邊的路徑,不難用中間的點來得到這個差。

  因此設所有邊兩兩之差的最大公約數爲 $d$,設 $g = (MOD, 3d)$,那麼 $(p, x)$ 和 $(p, x + g)$ 都是可以互相到達的。

  注意到此時任意一條邊的邊權爲 $kd + r$。先考慮一下 $r = 0$ 怎麼做。此時任意一個點的狀態都可以表示爲 $td$,我們可以把值對 $g$ 取模,因此我們只關心 $t$ 對 $3$ 取模後的餘數。然後就是一個點數只有 $3n$ 的圖判斷連通性,直接並查集維護就行了。

  現在考慮 $r \neq 0$ 的情形。注意到所有邊的 $r$ 都是相同的,最終路徑的權值是 $\sum_{i} 2^i (k_i d + r) = kd + (2^l- 1) r$ 。現在比較難處理的問題是 $2^l - 1$。考慮左後橫跳可以在不改變 $k\mod 3$ 的情況下使得 $l$ 增加 2。因此枚舉 $k$ 和 $l$ 的奇偶性,判斷是否存在一個 $l$ 使得 $kd + (2^l - 1) r = R$。現在狀態數只有 $6n$ 並查集維護即可。

Code

/**
 * AtCoder
 * AGC031F
 * Accepted
 * Time: 54ms
 * Memory: 2432k
 */
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

int n, m, q, N, Mod, g;
vector<int> uf;
vector<int> A, B, C;
vector<boolean> f[2];

int gcd(int a, int b) {
	return b ? gcd(b, a % b) : a;
}
int _abs(int x) {
	return x < 0 ? -x : x;
}

int find(int x) {
	return uf[x] == x ? x : (uf[x] = find(uf[x]));
}
void unit(int x, int y) {
	x = find(x), y = find(y);
	(x ^ y) && (uf[x] = y);
}

void init(vector<boolean>& f, int r) {
	f.resize(Mod, false);
	while (!f[r]) {
		f[r] = true;
		r = 4 * r % Mod;
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n >> m >> q >> Mod;
	uf.resize(N = 6 * n);
	A.resize(m);
	B.resize(m);
	C.resize(m);
	for (int i = 0; i < m; i++) {
		cin >> A[i] >> B[i] >> C[i];
		if (i) {
			g = gcd(g, _abs(C[i] - C[0]));
		}
	}
	if (!g)
		g = Mod;
	Mod = gcd(3 * g, Mod);
	int R = C[0] % g;
	for (int i = 0; i < N; i++)
		uf[i] = i;
	for (int i = 0; i < m; i++) {
		--A[i], --B[i], C[i] = (C[i] - R) / g;
		for (int k = 0; k < 3; k++) {
			for (int r = 0; r < 2; r++) {
				int cs = (k << 1) | r;
				int ns = (((k << 1) + C[i]) % 3) << 1 | (r ^ 1);
				unit(A[i] * 6 + cs, B[i] * 6 + ns);
				unit(A[i] * 6 + ns, B[i] * 6 + cs);
			}
		}
	}
	init(f[0], R);
	init(f[1], R * 2 % Mod);
	while (q--) {
		int s, t, r;
		cin >> s >> t >> r;
		boolean res = false;
		--s, --t;
		for (int k = 0; k < 3; k++) {
			int nv = (r + R - k * g) % Mod;
			(nv < 0) && (nv += Mod);
			for (int p = 0; p < 2; p++) {
				if (find(t * 6) == find(s * 6 + k * 2 + p)) {
					res = res || f[p][nv];
				}
			}
		}
		cout << ((res) ? ("YES") : ("NO")) << '\n';
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章