pekempeyのブログ

競技プログラミングに関する話題を書いていきます。

yukicoder 546: オンリー・ワン

https://yukicoder.me/problems/no/546

解法

高速メビウス変換というものを紹介する。高速メビウス変換というのは以下のような処理である。

for (int i = 0; i < n; i++) {
  for (int j = 0; j < 1 << n; j++) {
    if (~j >> i & 1) {
      a[j | 1 << i] -= a[j];
    }
  }
}

配列 a に以下の値をセットする。

a[000] = |U|
a[001] = |A|
a[010] = |B|
a[011] = |A*B|
a[100] = |C|
a[101] = |A*C|
a[110] = |B*C|
a[111] = |A*B*C|

この配列に対して高速メビウス変換を行うと以下のような結果が得られる。

a[000] = |U|
a[001] = |A| - |U|
a[010] = |B| - |U|
a[011] = |A*B| - |A| - |B| + |U|
a[100] = |C| - |U|
a[101] = |A*C| - |A| - |C| + |U|
a[110] = |B*C| - |B| - |C| + |U|
a[111] = |A*B*C| - |A*B| - |A*C| - |B*C| + |A| + |B| + |C| - |U|

この結果を使えばこの問題を解くことができる。原理は説明しないが、そんなに難しくない。

#include <iostream>
#include <algorithm>
#include <vector>

int64_t gcd(int64_t x, int64_t y) {
    if (y == 0) return x;
    return gcd(y, x % y);
}

int64_t lcm(int64_t x, int64_t y) {
    return std::min(1000000001LL, x / gcd(x, y) * y);
}

int64_t f(int64_t x, std::vector<int64_t> c) {
    const int n = c.size();
    std::vector<int64_t> cnt(1 << n);

    for (int i = 0; i < 1 << n; i++) {
        int64_t l = 1;
        for (int j = 0; j < n; j++) {
            if (i >> j & 1) {
                l = lcm(l, c[j]);
            }
        }
        cnt[i] = x / l;
    }

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < 1 << n; j++) {
            if (~j >> i & 1) {
                cnt[j | 1 << i] -= cnt[j];
            }
        }
    }

    int64_t ret = 0;
    for (int i = 0; i < n; i++) {
        int u = ((1 << n) - 1) ^ (1 << i);
        int v = ((1 << n) - 1);
        ret += std::abs(cnt[u]) - std::abs(cnt[v]);
    }

    return ret;
}

int main() {
    int64_t N, L, H;
    std::cin >> N >> L >> H;

    std::vector<int64_t> c(N);
    for (int i = 0; i < N; i++) {
        std::cin >> c[i];
    }

    std::cout << f(H, c) - f(L - 1, c) << std::endl;
}