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\),于是状态变化如下:

\[(a,ka+p)\to(ka+p,(k-1)a+p)\to((k-1)a+p,a)\to(a,(k-2)a+p) \]

发现三次操作后 \(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;
}
posted @ 2023-07-17 11:05  bykem  阅读(155)  评论(0)    收藏  举报