Official

E - チーム分けの最適化 / Optimizing Team Division Editorial by MMNMM


チームのまとまりの良さを、次のように言い換えます。

正の整数 \(g\) を固定する。 チームにはスキルレベルが \(g\) の倍数であるメンバーしか参加させることができない。 チームのまとまりの良さはチームに選ばれた人数を \(k\) 人として \(k\times g\) となる。

この言い換えで答えが変わらないことが示せます。

証明

まず、元の問題の答えが言い換えた後の問題の答え以下になることを示します。 次に、元の問題の答えが言い換えた後の問題の答え以上になることを示します。

元の問題において、まとまりの良さが最大になるチームについて考えます。 \(g\) としてそのときの最大公約数をとり、同じメンバーを選ぶことで元の問題の答えと同じまとまりの良さを達成することができます。 よって、言い換えたあとの問題の答えは元の問題の答え以上です。

言い換えたあとの問題において、できあがったチームのまとまりの良さは元の問題で同じメンバーから作ったチームのまとまりの良さ以下であることを示します。 \(g\) はメンバーのスキルレベルの公約数なので、特に最大公約数以下になります。 よって、言い換えたあとの問題で作ったチームのまとまりの良さは元の問題で同じメンバーから作ったチームのまとまりの良さ以下になります。 よって、言い換えたあとの問題の答えは元の問題の答え以下です。

よって、言い換えたあとの問題の答えは元の問題の答えと等しいことが示されました。

スキルレベルが \(g\) の倍数であるメンバーは全員チームに参加させるほうがよいです。 よって、次のような問題を解くことになります。

正の整数 \(g\) について、スキルレベルが \(g\) の倍数である参加者の人数を \(D _ g\) とする。\(g\times D _ g\) の最大値を求めよ。

スキルレベルが \(g\) である参加者の人数 \(C _ g\) が求められているとします。 このとき、\(\displaystyle D _ g=\sum _ {i=1} ^ \infty C _ {gi}\) です。 スキルレベルの最大値を \(M=10 ^ 6\) とすると、\(\displaystyle D _ g=\sum _ {i=1} ^ {\lfloor M/g\rfloor} C _ {gi}\) が成り立ちます。 この式に従って \(g=1,2,\ldots,M\) に対して \(g\times D _ g\) を求めると、\(\left\lfloor\dfrac M1\right\rfloor+\left\lfloor\dfrac M2\right\rfloor+\cdots+\left\lfloor\dfrac MM\right\rfloor=O(M\log M)\) 回だけ \(C _ i\) の足し算を行うことになります。

事前に \(O(N)\) 時間をかけて \(C _ i\) の値を求めておくことで、この問題を解くことができます。

実装例は以下のようになります。

#include <iostream>
#include <array>

int main(){
    using namespace std;
    constexpr int max_A{1000000};
    array<int, max_A + 1> count{}; // そのスキルレベルの人数を求めておく

    int N;
    cin >> N;
    for (int i = 0; i < N; ++i) {
        int A;
        cin >> A;
        ++count[A]; // カウンタを増やす
    }

    long ans = 0;
    for (int g = 1; g <= max_A; ++g) { // g ごとに
        int level_count = 0;
        for (int i = 1; i * g <= max_A; ++i) {
            level_count += count[i * g]; // g の倍数の人数を合計して
        }
        ans = max(ans, static_cast<long>(g) * level_count); // 答えを更新
    }

    // 答えを出力
    cout << ans << endl;
    return 0;
}
N = int(input())

count = [0 for _ in range(1000001)] # そのスキルレベルの人数を求めておく
for a in map(int, input().split()):
    count[a] += 1 # カウンタを増やす

ans = 0
for g in range(1, 1000001): # g ごとに
    ans = max(ans, g * sum(count[::g])) # g の倍数の人数を合計して答えを更新

print(ans)

posted:
last update: