難易度評価

難易度を1〜10段階で評価できます。投票はあとから変更・取り消しできます。

難易度(投票) 未投票 (0票)

投票の分布

まだ投票はありません。

  1. Lv.10票
  2. Lv.20票
  3. Lv.30票
  4. Lv.40票
  5. Lv.50票
  6. Lv.60票
  7. Lv.70票
  8. Lv.80票
  9. Lv.90票
  10. Lv.100票

難易度の目安を見る ↗

ログインして難易度を投票

外部サイトへ移動します

リンク先の URL を確認してから開いてください。

別タブで開く

Tournament Cards

作成者 frugq

難易度(作成者設定) Lv.5

難易度(投票) 未投票 (0票)

実行時間制限
2 秒
メモリ制限
512 MiB

解説

2つのDPを更新して求めます。

  • dp_byBattle[試合ID]: その試合終了時に、試合の勝者が持っている可能性のあるカードの数の集合
  • dp_byWinner[試合ID][その試合の勝者]: 試合で(その試合の勝者)が勝ったとき、持っている可能性のあるカードの数の集合

計算量はO(2NNM22^NNM^2)ですが、持っている数の集合をbitsetで持ち、いわゆる64倍高速化を行うことで、時間制限に間に合います。

数の集合を2進数に変換する方法

数の集合を以下のように2進数に変換します。

  • 集合{B1,B2,⋯ ,Bk}\{B_1, B_2, \cdots, B_k\}を、2B1+2B2+2Bk2^{B_1}+2^{B_2}+2^{B_k}に変換する。
  • 例えば、{0, 2, 5}は100101(2)100101_{(2)}に変換する。
試合後に持っている可能性のあるカードの数の集合の更新方法

集合{B1,B2,⋯Bk}\{B_1, B_2, \cdots B_k\}と集合{C1,C2,⋯Cl}\{C_1, C_2, \cdots C_l\}を持っており、前者が勝利した場合、前者は集合{2B1+C1,2B1+C2,⋯ ,2B1+CN,2B2+C1+2B2+C2,⋯ ,2Bk+Cl}\{2B_1+C_1, 2B_1+C_2,\cdots, 2B_1+C_N, 2B_2+C_1 + 2B_2 + C_2, \cdots, 2B_k + C_l\}の重複を除いたものを持ちます。

貪欲に行ってもこの更新の計算量はO(M2M^2)ですが、bitsetを用いた高速化を行います。

  • 簡単にするため、勝者のもつ集合の各要素をあらかじめ2倍に変換しておく。
  • 集合Bを2進数に変換したものをBbitB_{bit}、集合Cを2進数に変換したものをCbitC_{bit}とする。
  • 変換結果を表す変数resultresultを用意し、0で初期化する。
  • 0≤i<M0 \leq i < Mについて、CbitC_{bit}のii bit目が1であれば、resultresultをresultresult | (BbitB_{bit}をii回左シフトした値、B桁目以降は0桁目へローテートする)で更新する。

実装例(C++)

#include <iostream>
#include <bitset>
#include <vector>
using namespace std;
using ll = long long;
using BIT = bitset<109>;

ll N, M;

void bitShift(BIT& b){
    b <<= 1;
    if(b.test(M)){
        b.reset(M);
        b.set(0);
    }
}

BIT times2(BIT x){
    BIT result = 0;
    for(int i = 0; i < M; ++i){
        if(x.test(i)){
            result.set((2 * i) % M);
        }
    }
    return result;
}

BIT prod(BIT x, BIT y){
    BIT result = 0;
    for(int i = 0; i < M; ++i){
        if(y.test(i)){
            result |= x;
        }
        bitShift(x);
    }
    return result;
}

int main() {
    cin >> N >> M;
    ll cntPlayer = 1 << N;
    vector<ll> A(cntPlayer);
    for(int i = 0; i < cntPlayer; ++i){
        cin >> A.at(i);
    }
    vector<BIT> dp_byBattle(2 * cntPlayer, 0);
    vector<vector<BIT>> dp_byWinner(N + 1, vector<BIT>(cntPlayer, 0));
    
    for(int i = 0; i < cntPlayer; ++i){
        dp_byWinner.at(N).at(i).set(A.at(i));
        dp_byBattle.at(cntPlayer + i).set(A.at(i));
    }

    for(int i = cntPlayer - 1; i >= 1; --i){
        dp_byBattle.at(i) |= prod(times2(dp_byBattle.at(2 * i)), dp_byBattle.at(2 * i + 1));
        dp_byBattle.at(i) |= prod(dp_byBattle.at(2 * i), times2(dp_byBattle.at(2 * i + 1)));
    }

    for(int i = N - 1; i >= 0; --i){//ステージ数(逆から)
        int cntBattle = 1 << i;
        for(int j = 0; j < cntBattle; ++j){//対戦
            for(int k = 0; k < cntPlayer / cntBattle; ++k){//対戦での勝利者
                int id = j * (cntPlayer / cntBattle) + k;
                if(k < cntPlayer / cntBattle / 2){//対戦で左側
                    dp_byWinner.at(i).at(id) |= prod(times2(dp_byWinner.at(i + 1).at(id)), dp_byBattle.at(2 * ((1 << i) + j) + 1));
                }else{//対戦で右側
                    dp_byWinner.at(i).at(id) |= prod(times2(dp_byWinner.at(i + 1).at(id)), dp_byBattle.at(2 * ((1 << i) + j)));
                }
            }
        }
    }

    for(int i = 0; i < cntPlayer; ++i){
        vector<int> ans;
        for(int j = 0; j < M; ++j){
            if(dp_byWinner.at(0).at(i).test(j)){
                ans.push_back(j);
            }
        }
        cout << (int)ans.size();
        for(int e : ans){
            cout << " " << e;
        }
        cout << endl;
    }
    return 0;
}

ユーザー解説

ユーザー解説を書く

ユーザー解説はまだありません。