ながめも

競技プログラミングについて

Codeforces Round #654 (Div. 2) E - Asterism

E1 Easy Version 問題へのリンク

E2 Hard Version 問題へのリンク

問題概要

Yuzuはキャンディー x個を持っています。 N人の敵がいて、敵 i a_i個キャンディーを持っています。Yuzuは長さ Nの順列 Pの順番に従って敵と闘います。

戦闘では、Yuzuが敵以上のキャンディーを持っているとき勝利し、キャンディーを 1個得ます。負けた場合ゲームオーバーです。

すべての戦闘に勝利できる順列 Pはいくつあるでしょうか?という問題を考えることができますが、これでは面白くないです。

 f(x)をキャンディーの初期値が xのときのすべての先頭に勝利できる順列 Pの総数と定義します。

 N以下の素数 pが与えられるので、 f(x) pで割り切れない xをすべて列挙してください。

制約

解説

E1

制約が小さいので、 \Theta(N^{2})を考えます。すべての xの候補に対して \Theta(N)かけてもよいことになります。数列 aを昇順ソートして、先頭から候補数を尺取り法で決めていくことができ、それをかけていけば f(x)が求まります。これが素数 pで割り切れるとき、前から決めた候補数に pの倍数があることになります。

E2

まず、 f(x) > 0となる最小の xを求めます。これはE1と二分探索を使えば \Theta(N\log N)でできます。

ここで、候補数に pが出てくるかどうかは、単調性があります(解いたときは未証明だったが、実験していると割と自明だと思いました)。この単調性に気づけると、先ほど求めた f(x) > 0になる最小の x以上 inf未満で二分探索をして、 pが出てくるかどうかを O(N)で確認すればよいです。

実験は 2,3,4,9,9,9,10でやってみました。

証明は公式editorialに書いてあったので確認の意味も込めて書いておこうと思います。

 b_i i個以下のキャンディーを持っている敵の数と定義し、  C_i(x) = b_{x + i} - iとおくと

  •  \displaystyle {f(x) = \prod_{i = 0}^{N-1}C_i(x)}
  • 一度でも C_i(x) p以上の値をとると、 C_i(x)は必ず pで割り切れる。なぜなら、 C_{N-1}(x) = 1であり、 C_i(x)は一つ進むごとに最大で 1ずつしか下がらないので、どこかに pが存在するからである。
  •  C_i(x) \le C_i(x+1)が成り立つ
  • 以上より、 f(x) pで割り切れるならば、 f(x + \alpha) pで割り切れる、つまり単調性があることが示された。

実装

  • E1
int main() {

    ll N, p;
    cin >> N >> p;
    vector<ll> a(N);
    rep(i, N)cin >> a[i];
    sort(all(a));

    vector<ll> ans;
    for(ll xx = 1; xx <= a[N-1]; xx++){
        ll x = xx;
        bool ok = true;
        int l = 0;
        int r = 0;
        rep(l,N){
            while(r < N && a[r] <= x){
                r++;
            }
            if((r - l) <= 0)ok = false;
            if((r - l) % p == 0)ok = false;
            x++;
        }
        if(ok)ans.push_back(xx);
    }

    cout << ans.size() << endl;
    for(auto &val: ans){
        cout << val << " ";
    }
    cout << endl;
}
  • E2
int main() {

    ll N, p;
    cin >> N >> p;
    map<ll,ll> mp;
    vector<ll> a(N);
    rep(i, N){
        cin >> a[i];
        mp[a[i]]++;
    }
    sort(all(a));
    ll ok = INFLL;
    ll ng = 0;
    auto check = [&](ll X){
        int r = 0;
        for(int l = 0; l < N; l++){
            while(r < N && a[r] <= X){
                r++;
            }
            if((r - l) <= 0)return false;
            X++;
        }
        return true;
    };
    while(abs(ng - ok) > 1){
        ll mid = (ok + ng) >> 1;
        if(check(mid))ok = mid;
        else ng = mid;
    }
    ll ok2 = ok-1;
    ll ng2 = INFLL;
    auto check2 = [&](ll X){
        int r = 0;
        rep(l,N){
            while(r < N && a[r] <= X){
                r++;
            }
            if((r - l) % p == 0)return false;
            X++;
        }
        return true;
    };
    while(ng2 - ok2 > 1){
        ll mid = (ng2 + ok2) >> 1;
        if(check2(mid))ok2 = mid;
        else ng2 = mid;
    }
    cout << ok2 - ok + 1 << endl;
    for(ll ans = ok; ans <=ok2; ans++){
        cout << ans << " ";
    }
    cout << endl;
}