JOI 春合宿2015 コピー&ペースト2

問題概要

文字列 S が与えられる.
S に対して,クエリが N 個投げられる.
各クエリは A, B, C を持ち,これは S の部分文字列 [A, B) を位置 C の直前にコピー挿入することを意味する.
最終的にできた S の先頭 K 文字を答えよ.
ただし,操作の途中で M 文字を超えた場合は,M 文字以降を削るものとする.

・制約
1 <= K <= 200
1 <= M <= 10^9
K <= S <= min(M, 2 * 10^5)
1 <= N <= 2 * 10^5

解法

K が小さいことに気が付かず,平衡二分探索木を書き始めて辛い思いをしていた.
実際,K が大きければこれで良い.
今回は 1 <= K <= 200 なので,O(KN) が間に合う.
やり方は,操作を逆順に見るという定番のテクで求めていく.

今 t 番目の文字が,ある操作の直前にどこにあったかを考えれば,最終的にどの文字になるかがわかる.
これをすべての 0 <= t < K に対してやる.
1. i 番目の操作で t 番目の文字が変化しない
2. i 番目の操作で t 番目の文字は挿入された文字の一部(overlap)になる.
3. i 番目の操作で t 番目の文字は挿入によって単に後ろにずれる.
のいずれかだから,場合分けしてやればよい.

ソースコード

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

int main() {
    int K, M, N;
    string S;
    cin >> K >> M >> S >> N;
    vector<int> A(N), B(N), C(N);
    for(int i = N - 1; i >= 0; --i) {
        cin >> A[i] >> B[i] >> C[i];
    }
    string res;
    for(int i = 0; i < K; ++i) {
        int t = i;
        for(int j = 0; j < N; ++j) {
            if(t < C[j]) {
                continue;
            } else if(t < C[j] + (B[j] - A[j])) {
                t = A[j] + t - C[j];
            } else {
                t = t - (B[j] - A[j]);
            }
        }
        res += S[t];
    }
    cout << res << endl;
}

感想

制約はちゃんと読もうね.