zoukankan      html  css  js  c++  java
  • 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 eq 0$ 的情形。注意到所有边的 $r$ 都是相同的,最终路径的权值是 $sum_{i} 2^i (k_i d + r) = kd + (2^l- 1) r$ 。现在比较难处理的问题是 $2^l - 1$。考虑左后横跳可以在不改变 $kmod 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")) << '
    ';
    	}
    	return 0;
    }
  • 相关阅读:
    【数量技术宅|金融数据分析系列分享】为什么中证500(IC)是最适合长期做多的指数
    异常控制流
    链接
    最小生成树的Prim算法(待修正版)
    最小生成树的Kruskal算法
    优先队列用法(转载)
    不相交集合的链表实现
    寻找通用汇点
    找零问题
    【Angular06】管道(类似vue的过滤器)、变更检测的工作原理
  • 原文地址:https://www.cnblogs.com/yyf0309/p/12864465.html
Copyright © 2011-2022 走看看