CF1848C Vika and Price Tags 题解
每组 \((a_i,b_i)\) 之间相互独立,于是我们只需要考虑一对数 \((a,b)\)。
首先 \((0,0)\) 的情况是平凡的。考虑我们需要的状态 \((0,b)\),它的变化过程是这样的:\((0,b)\to(b,b)\to(b,0)\to(0,b)\),所以是三轮一个循环,且除了 \((0,b)\) 之外另外两组状态都不合法。我们求出每组 \((a_i,b_i)\) 变成 \((0,x)\) 需要的步数 \(s_i\),那么答案为 YES 当且仅当对所有 \(1\le i,j\le n\) 都有 \(s_i\equiv s_j\pmod 3\),证明是显然的。
那么问题变成了怎么快速求出 \(s_i\),我们参考辗转相除法,假设 \(a<b\),那么可以将 \(b\) 表示成 \(b=ka+p\),其中 \(0\le p<a\),于是状态变化如下:
发现三次操作后 \(k\) 变成了 \(k-2\),又因为我们只在乎 \(s_i\bmod 3\) 的值,所以可以直接将 \(k\) 对 \(2\) 取模。取模后如果 \(k=1\),那么变化流程如下:\((a,a+p)\to(a+p,p)\to(p,a)\),我们使用 \(2\) 次操作将其变成 \((p,a)\),该状态可以递归处理;否则直接递归处理 \((a,p)\)。
若 \(a>b\),变化是类似的,三步操作使 \(k\) 减二,最后如果 \(k=1\),变化流程如下:\((b+p,b)\to(b,p)\),一次操作将其变成 \((b,p)\) 后递归处理。
最后递归终止状态为 \(a=0\) 或 \(b=0\),这两种情况是 trival 的。
时间复杂度证明和辗转相除法一样,总时间复杂度为 \(\mathcal{O}(n\log v)\)。
#include <algorithm>
#include <iostream>
using namespace std;
using LL = long long;
const int kN = 1e5 + 1;
int t, n, a[kN], b[kN], c[3];
int C(int a, int b) {
if (!a || !b) {
return !!a;
}
if (a < b) {
int k = b / a, p = b % a;
return (k & 1) ? 2 + C(p, a) : C(a, p);
} else {
int k = a / b, p = a % b;
return (k & 1) ? 1 + C(b, p) : C(p, b);
}
}
int main() {
ios_base::sync_with_stdio(0), cin.tie(0);
for (cin >> t; t--;) {
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
for (int i = 1; i <= n; ++i) {
cin >> b[i];
}
c[0] = c[1] = c[2] = 0;
for (int i = 1; i <= n; ++i) {
if (a[i] || b[i]) {
++c[C(a[i], b[i]) % 3];
}
}
cout << (count(c, c + 3, 0) >= 2 ? "YES" : "NO") << '\n';
}
return 0;
}

浙公网安备 33010602011771号