けんちょんの競プロ精進記録

競プロの精進記録や小ネタを書いていきます

Codeforces Round #511 (Div. 1) A - Enlarge GCD (R1800)

大昔 osa_k 法と呼ばれていたやつ。

問題へのリンク

問題概要

 N 個の整数  a_1, a_2, \dots, a_N が与えられ、 G をこれらの最大公約数とする。

今、 N 個の整数から何個かを取り除いて、その最大公約数が  G より大きくなるようにしたい。取り除くべき最小個数を求めよ。ただし、どのように取り除いても不可能な場合は -1 とせよ。

制約

  •  2 \le N \le 3×10^{5}
  •  1 \le a_i \le 15000000

解法

まずは各  a を最大公約数で割っておく。
素数  p について、何個の  a で割れるかの最大値を求めて  n から引けば OK。

15000000 以下の素数を全部試していてはつらいので、a の素因子となるものだけを試したい。そのようなものは、以下のようにして高速に実現できる。

  • MinFactor[ n ] := n に含まれる最小の素因子

この配列はエラトステネスの篩を応用することで高速に計算できて、この配列を用いると、各整数の素因数分解を超高速に実現できる。

#include <iostream>
#include <vector>
using namespace std;


const int MAX = 15001000;
bool IsPrime[MAX];
int MinFactor[MAX];
vector<int> preprocess(int n = MAX) {
    vector<int> res;
    for (int i = 0; i < n; ++i) IsPrime[i] = true, MinFactor[i] = -1;
    IsPrime[0] = false; IsPrime[1] = false; 
    MinFactor[0] = 0; MinFactor[1] = 1;
    for (int i = 2; i < n; ++i) {
        if (IsPrime[i]) {
            MinFactor[i] = i;
            res.push_back(i);
            for (int j = i*2; j < n; j += i) {
                IsPrime[j] = false;
                if (MinFactor[j] == -1) MinFactor[j] = i;
            }
        }
    }
    return res;
}

vector<pair<int,int> > prime_factor(int n) {
    vector<pair<int,int> > res;
    while (n != 1) {
        int prime = MinFactor[n];
        int exp = 0;
        while (MinFactor[n] == prime) {
            ++exp;
            n /= prime;
        }
        res.push_back(make_pair(prime, exp));
    }
    return res;
}


// 最大公約数
int GCD(int x, int y) { return y ? GCD(y, x%y) : x; }


int main() {
    // 入力
    int n; scanf("%d", &n);
    vector<int> a(n);
    for (int i = 0; i < n; ++i) scanf("%d", &a[i]);

    // 最大公約数で割っておく
    int g = 0;
    for (int i = 0; i < n; ++i) g = GCD(g, a[i]);
    for (int i = 0; i < n; ++i) a[i] /= g;

    // 各素因子ごとに a に何個あるかをカウント
    preprocess();
    vector<int> count(MAX, 0);
    for (int i = 0; i < n; ++i) {
        auto pf = prime_factor(a[i]);
        for (auto p : pf) count[p.first]++;
    }
    int res = 0;
    for (int i = 0; i < MAX; ++i) res = max(res, count[i]);
    if (res == 0) cout << -1 << endl;
    else cout << n-res << endl;
}