DDCC 2018 D - DISCO!

解法

区間同士でうまくマージできるような形であればセグ木に乗せることができるので、とりあえずその方針で考えてみる。
2つの区間 A, B があって、それらをマージしたときに "DISCO" がいくつ現れるかを考えてみると

  • A から "", B から "DISCO"
  • A から "D", B から "ISCO"
  • A から "DI", B から "SCO"
  • A から "DIS, B から "CO"
  • A から "DISC", B から "O"
  • A から "DISCO", B から ""

の5通りある。
よって、それぞれの区間に、対応する文字列が何通りあるか持てば良いことがわかる。
この議論は、当然 "ISCO" や "SCO" などを数えるときにも必要であるが、
冷静に考えると、文字列は "DISCO" の連続部分文字列であり、先程列挙したように、文字列の構成の仕方に特徴がある。
これに気がつけば
A[i][j] := "DISCO" の [i, j) が完成している場合の数
と定義した行列積が、区間のマージに対応することがわかる。

たとえば、s[i] が 'I' であるなら、A[1][2] = 1 とすればよい(ただし対角成分は常に1)。

以上で、計算量は O(6^3 * Q * logN) となる。
TL が 13sec なので普通に書けば通るだろう。

ソースコード

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

using u32 = uint32_t;
using matrix = array<array<u32, 6>, 6>;

matrix make_zmat() {
    matrix res;
    for(int i = 0; i < 6; ++i) {
        for(int j = 0; j < 6; ++j) {
            res[i][j] = 0;
        }
    }
    return res;
}

matrix make_id_mat() {
    auto res = make_zmat();
    for(int i = 0; i < 6; ++i) res[i][i] = 1;
    return res;
}

matrix operator*(matrix const& a, matrix const& b) {
    auto res = make_zmat();
    for(int k = 0; k < 6; ++k) {
        for(int i = 0; i < 6; ++i) {
            for(int j = 0; j < 6; ++j) {
                res[i][j] += a[i][k] * b[k][j];
            }
        }
    }
    return res;
}

template<typename Monoid>
class segment_tree {
    using T = typename Monoid::type;

public:
    segment_tree(std::vector<T> const& init)
        : sz(init.size()), n(expand(init.size()))
    {
        dat.assign(n*2, Monoid::id());
        std::copy(begin(init), end(init), begin(dat) + n);
        for(int i = n - 1; i >= 0; --i) {
            dat[i] = Monoid::op(dat[i * 2], dat[i * 2 + 1]);
        }
    }

    segment_tree(int const n_, T const& init = Monoid::id())
        : segment_tree(std::vector<T>(n_, init))
    {}

    void update(int p, T val) {
        assert(0 <= p && p < sz);
        dat[p += n] = val;
        while(p /= 2) {
            dat[p] = Monoid::op(dat[p * 2], dat[p * 2 + 1]);
        }
    }

    // [l, r)
    T query(int l, int r) const {
        assert(0 <= l && l <= r && r <= sz);
        l += n, r += n;
        T res1 = Monoid::id(), res2 = Monoid::id();
        while(l != r) {
            if(l & 1) res1 = Monoid::op(res1, dat[l++]);
            if(r & 1) res2 = Monoid::op(dat[--r], res2);
            l /= 2, r /= 2;
        }
        return Monoid::op(res1, res2);
    }

private:
    int expand(int n_) const {
        assert(n_ >= 1);
        return n_ == 1 ? n_ : expand((n_ + 1) / 2) * 2;
    }

private:
    const int sz, n;
    std::vector<T> dat;
};

struct DISCO {
    using type = matrix;

    static type id() {
        return make_id_mat();
    }
    static type op(type const& l, type const& r) {
        return l * r;
    }
};

int main() {
    string s; cin >> s;
    const int n = s.size();

    segment_tree<DISCO> seg(n);
    const string disco = "DISCO";
    for(int i = 0; i < n; ++i) {
        auto a = make_id_mat();
        const int idx = find(begin(disco), end(disco), s[i]) - begin(disco);
        a[idx][idx + 1] = 1;
        seg.update(i, a);
    }

    int q; cin >> q;
    while(q--) {
        int l, r; cin >> l >> r;
        l--;
        cout << seg.query(l, r)[0][5] << endl;
    }
}

感想

なぜか本番で解けなかったやつ。大反省。