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

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

JOI 予選 2007 D - カードの並び替え (AOJ 0513, 難易度 3)

難しくはないけど、やることが複雑だし、問題文を読み解くのがハードな問題! この手の問題は、解けないと思ったら、実際のコードを見てしまう方が早いと思う。

問題概要

正の整数  N が与えられる。

1 から  2N までの数値が書かれたカードがこの順に上から積み上がっている。この山に対して、 M 回の操作を実行する。各操作は非負整数値  K で示され、

  •  K = 0 のとき:リフルシャッフルをする (問題文参照)
  •  K \ge 1 のとき:整数  K でカットをする (問題文参照)

とする。 M 回操作後のカードの山の各数値を答えよ。

考えたこと

特に特別な工夫をすることなく、問題文で言われたことをそのまま実装すれば正解となる。このように「言われたままに実装すればよい」という解法を、愚直シミュレーションと呼ぶことがある。

愚直シミュレーションは軽視されがちではあるものの、今後あらゆるアルゴリズムを実装する上での実装力の礎となる。こうした問題にも真摯に取り組みたいところ。

関数を活用

工夫のしどころとしては、「リフルシャッフル」「整数  K でカット」という操作を実行する部分を関数として切り出すと良いと思う。プログラム全体が見やすくなるし、WA が取れないとき、関数の部分だけをテストするなどしてデバッグしやすくなる。

コード

#include <bits/stdc++.h>
using namespace std;

// 2N 枚のカードをリフルシャッフルする関数
vector<int> rifuru(vector<int> cards, int N) {
    // 2N 枚のカードを前半と後半に分ける
    vector<int> zenhan, kouhan;
    for (int i = 0; i < N; ++i) zenhan.push_back(cards[i]);
    for (int i = N; i < N*2; ++i) kouhan.push_back(cards[i]);
    
    // zenhan と kouhan を互いに違いに入れていく
    vector<int> res;
    for (int i = 0; i < N; ++i) {
        res.push_back(zenhan[i]);
        res.push_back(kouhan[i]);
    }
    return res;
}

// 整数 k でカットする関数
vector<int> cut(vector<int> cards, int N, int K) {
    vector<int> res;
    
    // 後半 2N-K 枚を先に挿入していく
    for (int i = K; i < N*2; ++i) res.push_back(cards[i]);
    
    // 前半 K 枚を挿入していく
    for (int i = 0; i < K; ++i) res.push_back(cards[i]);
    
    return res;
}

int main() {
    int N, M;
    cin >> N >> M;
    
    // 1〜2N のカードを用意する
    vector<int> cards(N*2, 0);
    for (int i = 0; i < N*2; ++i) cards[i] = i+1;
    
    // 順に実行していく
    for (int i = 0; i < M; ++i) {
        int K;
        cin >> K;
        if (K == 0) cards = rifuru(cards, N);
        else cards = cut(cards, N, K);
    }
    
    // 結果を出力
    for (int i = 0; i < N*2; ++i) cout << cards[i] << endl;
}