Codeforces Round #407 (Div. 2) D. Weird journey

解法

まず,同じ辺を2回通るパスという時点でオイラーツアーが思い浮かびます.
自己辺を考えるのはめんどうなので,とりあえずない場合を考えると,条件を満たすパスがあるかどうかは,「一度のDFSによって辿れる頂点の次数の総和が,辺の個数 m の2倍になっている」ことと同値である(よね?)とわかります.
この時,隣接する任意の2つの辺について,それら2つを1回だけ通り,その他の辺を2回通るパスが明らかに存在します.
なので,この隣接する2辺のペアを適当に求めます(1つの頂点に着目して,出てる辺から2つ選べば良いです).
最後に自己辺ですが,まあ実験すればすぐわかるんですけど,こいつらは自由に使えます.
なので,最後に 自己辺の数 * (自己辺を除くその他の辺) + (自己辺から2つ選ぶ選び方) を足してやれば解けます.

ソースコード

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

using ll = long long;
using pii = pair<int, int>;

using graph = vector<vector<int>>;

int dfs(graph const& g, int v, int p, vector<bool>& used) {
    int res = g[v].size();
    used[v] = true;
    for(auto to : g[v]) {
        if(to == p || used[to]) {
            continue;
        }
        res += dfs(g, to, v, used);
    }
    return res;
}

int main() {
    int n, m;
    scanf("%d %d", &n, &m);
    graph g(n);
    int root = -1;
    vector<vector<int>> v(n);
    int self_cnt = 0;
    for(int i = 0; i < m; ++i) {
        int a, b;
        scanf("%d %d", &a, &b);
        a--; b--;
        root = a;
        if(a == b) {
            g[a].push_back(a);
            self_cnt++;
        } else {
            g[a].push_back(b);
            g[b].push_back(a);
            v[a].push_back(i);
            v[b].push_back(i);
        }
    }

    vector<bool> used(n);
    if(dfs(g, root, -1, used) + self_cnt != 2 * m) {
        printf("0\n");
    } else {
        ll res = 0;
        for(auto x : v) {
            if(x.size() != 0) {
                res += x.size() * (x.size() - 1) / 2;
            }
        }
        res += self_cnt * (m - self_cnt);
        res += self_cnt * (self_cnt - 1) / 2;
        printf("%lld\n", res);
    }
}

感想

あくまで辺を頂点と見たグラフが連結であればよいので,頂点自体が連結である必要はないです(罠).
あと入力が 10^6 だったのを忘れて cin で TLE 食らってしまった.

Codeforces Round #404 (Div.2) E. Anton and Permutation

解法

平方分割 + 2次元BITで解いた.[0...m] x [0...n] で二次元.m は n / sqrt(n).
l[i] から r[i] の間に完全に含まれているブロックは BIT 上で計算し,端っこの部分は生で持っている配列を使って計算する.
bit.sum(lb, a, rb, b) とすると lb から rb - 1 までのブロックにおける [a..b) の数を求められる.

計算量は O(Q * (sqrt(N) + log^2(N))).

ソースコード

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

using ll = long long;

template <typename T>
class fenwick_tree2d {
public:
    fenwick_tree2d(int h, int w)
        : H(h), W(w), dat(H, std::vector<T>(W))
    {}

    void add(int i, int j, T value) {
        for(; i < H; i |= i + 1) {
            int jj = j;
            for(; jj < W; jj |= jj + 1) {
                dat[i][jj] += value;
            }
        }
    }

    // [0..i] x [0..j]
    T sum(int i, int j) const {
        T res = 0;
        for(; i >= 0; i = (i & (i + 1)) - 1) {
            int jj = j;
            for(; jj >= 0; jj = (jj & (jj + 1)) - 1) {
                res += dat[i][jj];
            }
        }
        return res;
    }

    // [i1...i2) x [j1...j2)
    T sum(int i1, int j1, int i2, int j2) const {
        return sum(i2 - 1, j2 - 1) - sum(i1 - 1, j2 - 1) - sum(i2 - 1, j1 - 1) + sum(i1 - 1, j1 - 1);
    }

private:
    int const H, W;
    std::vector<std::vector<T>> dat;
};

int main() {
    int n, q;
    cin >> n >> q;
    vector<int> l(q), r(q);
    for(int i = 0; i < q; ++i) {
        cin >> l[i] >> r[i];
        l[i]--; r[i]--;
        if(l[i] > r[i]) {
            swap(l[i], r[i]);
        }
    }

    vector<int> a(n);
    for(int i = 0; i < n; ++i) {
        a[i] = i;
    }
    int const B = sqrt(n) + 1;
    fenwick_tree2d<int> bit(n / B + 1, n);
    for(int i = 0; i < n; ++i) {
        bit.add(i / B, i, 1);
    }

    ll inv = 0;
    for(int i = 0; i < q; ++i) {
        if(l[i] != r[i]) {
            int lb = l[i] / B, rb = r[i] / B;
            if(lb + 1 < rb) {
                int l_lt = bit.sum(lb + 1, 0, rb, a[l[i]]);
                int l_gt = bit.sum(lb + 1, a[l[i]] + 1, rb, n);
                int r_lt = bit.sum(lb + 1, 0, rb, a[r[i]]);
                int r_gt = bit.sum(lb + 1, a[r[i]] + 1, rb, n);
                inv += l_gt - l_lt + r_lt - r_gt;
            }
            if(lb == rb) {
                for(int j = l[i] + 1; j < r[i]; ++j) {
                    inv += (a[l[i]] < a[j] ? 1 : -1) + (a[r[i]] < a[j] ? -1 : 1);
                }
            } else {
                for(int j = l[i] + 1; j < (lb + 1) * B; ++j) {
                    inv += (a[l[i]] < a[j] ? 1 : -1) + (a[r[i]] < a[j] ? -1 : 1);
                }
                for(int j = rb * B; j < r[i]; ++j) {
                    inv += (a[l[i]] < a[j] ? 1 : -1) + (a[r[i]] < a[j] ? -1 : 1);
                }
            }
            inv += (a[l[i]] < a[r[i]] ? 1 : -1);

            bit.add(l[i] / B, a[l[i]], -1);
            bit.add(r[i] / B, a[r[i]], -1);
            swap(a[l[i]], a[r[i]]);
            bit.add(l[i] / B, a[l[i]], 1);
            bit.add(r[i] / B, a[r[i]], 1);
        }
        cout << inv << endl;
    }
}

感想

メモリもTLEも怖すぎる.

Codeforces Round #404 (Div.2) D. Anton and School - 2

解法

条件に合う部分列の,一番右端の "(" の位置を総当りして計算します.
その位置から左に(その位置自体も含めて)"(" が $x$ 個あり,右に ")" が $y$ 個ある時,求める答えは $\displaystyle \sum_{i = 1}^{\min (x, y)}({_{x-1}C_{i-1}} \times {_yC_i})$ となります.簡単のため $x < y$ の時を考えます.
右辺の各 i についての式 $_{x-1}C_{i-1} \times _yC_i$ が意味するものを考えます.式変形して,$_{x-1}C_{x-i} \times _yC_i$ としておきます.
これは $x - 1$ 人からなるグループから $x - i$ 人を選び,$y$ 人からなるグループから $i$ 人を選ぶ組み合わせに対応します.つまり,$x - 1 + y$ 人から $x$ 人を選ぶ方法のうち,$x - 1$ 人の方から $x - i$ 人を選ぶときの組み合わせに対応します.
すると,$i$ を $1$ から $x$ まで動かした時,$x - 1 + y$ 人から $x$ 人を選ぶ方法すべてを網羅することになります.よって,
$\displaystyle \sum_{i = 1}^{x}({_{x-1}C_{i-1}} \times {_yC_i}) = _{x+y-1}C_x$
が成り立ち,求めたい値を O(1) で求めることができます.(前計算は必要です.)

・おまけ
ちょっと一般化すると
$\displaystyle \sum_{i=0}^z{_xC_i}{_yC_{z-i}} = _{x+y}C_{z}~ (z \leq x)$
で,$z = x$ とすれば
$\displaystyle \sum_{i=0}^x{_xC_i}{_yC_i} = _{x+y}C_{x}$

ソースコード

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

using ll = long long;

// modulo は略

const int MOD = 1000000007;
using mod = modulo<MOD, true>;

int main() {
    string s;
    cin >> s;
    int n = s.size();
    vector<int> left(n + 1), right(n + 1);
    for(int i = 0; i < n; ++i) {
        left[i + 1] = left[i] + (s[i] == '(');
        right[n - i - 1] = right[n - i] + (s[n - i - 1] == ')');
    }
    mod res = 0;
    for(int i = 1; i <= n; ++i) {
        if(s[i - 1] != '(') {
            continue;
        }
        if(right[i] != 0) {
            res += comb<MOD>(left[i] + right[i] - 1, left[i]);
        }
    }
    cout << (ll)res << endl;
}

感想

式はすぐ出て来るが,変形で詰まった.

Codeforces ECR #31 F. Anti-Palindromize

解法

最小費用流で解ける.
まず,各アルファベットに対応する26個の頂点と,N / 2 個からなる文字列の前半(ないし後半)に対応する頂点をもつグラフをつくる.
それぞれのアルファベットから,文字列の各位置に対して辺を張る.
以下ではアルファベットの頂点番号を N / 2 + (c - 'a') とする.
頂点 N / 2 + (c - 'a') から頂点 i (0 <= i < N / 2) にフローが流れることを,S[i] または S[N - 1 - i] にアルファベット c を配置するということに対応付ける.
こうする時,辺のコストは max(S[i] == c ? b[i] : 0, S[N - 1 - i] == c ? b[N - 1 - i] : 0) とすることができる.
もちろん容量は1である.
source からは各アルファベットに対して辺を張り,コストは0,容量はそれぞれのアルファベットの出現数とする.
また,文字列の各位置に対応する頂点から sink に辺を張るのだが,これはコスト0,容量2としておく.
以上のように辺をはると,同じ頂点 i (0 <= i < N / 2) に対して同じ文字は割り当てられず,それぞれの頂点にちょうど2個のアルファベットを割り当てることができる.

ソースコード

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

using ll = long long;

using weight = int;

constexpr int INF = 1e9;

struct edge {
    int to, cap;
    weight cost;
    int rev;
};

using edges = std::vector<edge>;
using graph = std::vector<edges>;

void add_edge(graph& g, int from, int to, int cap, weight cost) {
    g[from].push_back(edge{to, cap, cost, (int)g[to].size()});
    g[to].push_back(edge{from, 0, -cost, (int)g[from].size()-1});
}

weight min_cost_flow(graph& g, int s, int t, weight f) {
    using P = std::pair<weight, int>;
    weight res = 0;
    std::vector<weight> h(g.size(), 0);
    std::vector<weight> dist(g.size());
    std::vector<int> prevv(g.size()), preve(g.size());
    while(f > 0) {
        std::priority_queue<P, std::vector<P>, std::greater<P>> que;
        std::fill(dist.begin(), dist.end(), INF);
        dist[s] = 0;
        que.push(P{dist[s], s});
        while(!que.empty()) {
            P p = que.top(); que.pop();
            int v = p.second;
            if(dist[v] < p.first) {
                continue;
            }
            for(int i = 0; i < (int)g[v].size(); ++i) {
                edge& e = g[v][i];
                if(e.cap > 0 && dist[e.to] > dist[v] + e.cost + h[v] - h[e.to]) {
                    dist[e.to] = dist[v] + e.cost + h[v] - h[e.to];
                    prevv[e.to] = v;
                    preve[e.to] = i;
                    que.push(P{dist[e.to], e.to});
                }
            }
        }
        if(dist[t] == INF) {
            return -1;
        }
        for(int v = 0; v < (int)g.size(); ++v) {
            h[v] += dist[v];
        }

        weight d = f;
        for(int v = t; v != s; v = prevv[v]) {
            d = min(d, g[prevv[v]][preve[v]].cap);
        }
        f -= d;
        res += d * h[t];
        for(int v = t; v != s; v = prevv[v]) {
            edge& e = g[prevv[v]][preve[v]];
            e.cap -= d;
            g[v][e.rev].cap += d;
        }
    }
    return res;
}


int main() {
    int N;
    string S;
    cin >> N >> S;
    vector<int> b(N);
    for(int i = 0; i < N; ++i) {
        cin >> b[i];
    }
    vector<int> cnt(26);
    for(auto c : S) {
        cnt[c - 'a']++;
    }

    graph g(N / 2 + 26 + 2);
    int const source = N / 2 + 26;
    int const sink = source + 1;
    for(int i = 0; i < 26; ++i) {
        add_edge(g, source, N / 2 + i, cnt[i], 0);
    }
    for(int i = 0; i < N / 2; ++i) {
        add_edge(g, i, sink, 2, 0);
        for(int j = 0; j < 26; ++j) {
            int cost = 0;
            if(S[i] - 'a' == j) {
                cost = max(cost, b[i]);
            }
            if(S[N - 1 - i] - 'a' == j) {
                cost = max(cost, b[N - 1 - i]);
            }
            add_edge(g, N / 2 + j, i, 1, -cost);
        }
    }

    cout << -min_cost_flow(g, source, sink, N) << endl;
}

AOJ 2294 Entangled with Lottery

解法

ゴールから見るDPでやります.

dp[i][j][k] := 高さ (H から) i まで見た時に,位置 j にいて,それまでに猫が k 本引いているような確率

とします.
高さ 1 から i までに,うさぎがすでに引いている本数を m とすると,猫が引ける高さは残り i - m 箇所です.
今見ている高さがすでにうさぎによって線が引かれていれば,単純に swap するだけです.
引かれていないなら,今見ている高さに猫が線を引く確率 q は,
$\displaystyle q = \frac{_{i - m - 1}C_{K - k - 1}}{_{i - m}C_{K - k}} = \frac{K - k}{i - m}$
となります.
引いた上で,今見ている位置 j に関係する部分に線が引かれる確率 p は,端ならば $\displaystyle p = \frac{1}{N - 1}$,端でなければ $\displaystyle \frac{2}{N - 1}$ です.
あとは,dp[i][j][k] を,「その高さに線をそもそも引かない」「その高さに線を引き,かつそれが j に関係する」「その高さに線を引き,かつそれが j に関係しない」場合の3つに遷移させるだけです.

ソースコード

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

using pii = pair<int, int>;

int main() {
    cout << fixed << setprecision(10);
    int H, N, P, M, K;
    cin >> H >> N >> P >> M >> K;
    P--;
    vector<int> sw(H, -1);
    for(int i = 0; i < M; ++i) {
        int a, b;
        cin >> a >> b;
        sw[a - 1] = b - 1;
    }

    vector<int> sum(H + 1);
    for(int i = 0; i < H; ++i) {
        sum[i + 1] = sum[i] + (sw[i] != -1);
    }
    vector<vector<double>> dp(N, vector<double>(K + 1));
    dp[P][0] = 1;
    double p = 1.0 / (N - 1);
    for(int i = H - 1; i >= 0; --i) {
        vector<vector<double>> nxt(N, vector<double>(K + 1));
        for(int j = 0; j < N; ++j) {
            for(int k = 0; k <= K; ++k) {
                if(dp[j][k] == 0) {
                    continue;
                }
                if(sw[i] == -1) {
                    double q = (double)(K - k) / (i + 1 - sum[i]); // put possibility
                    if(k == K) {
                        nxt[j][k] += dp[j][k] * (1 - q); // (1 - q) == 1
                    } else {
                        if(j != 0) {
                            nxt[j - 1][k + 1] += dp[j][k] * p * q;
                        }
                        if(j != N - 1) {
                            nxt[j + 1][k + 1] += dp[j][k] * p * q;
                        }
                        nxt[j][k] += dp[j][k] * (1 - q);
                        if(j == 0 || j == N - 1) {
                            nxt[j][k + 1] += dp[j][k] * q * (N - 2) / (N - 1);
                        } else {
                            nxt[j][k + 1] += dp[j][k] * q * (N - 3) / (N - 1);
                        }
                    }
                } else {
                    if(sw[i] == j) {
                        nxt[sw[i] + 1][k] += dp[j][k];
                    } else if(sw[i] + 1 == j) {
                        nxt[sw[i]][k] += dp[j][k];
                    } else {
                        nxt[j][k] += dp[j][k];
                    }
                }
            }
        }
        dp = move(nxt);
    }

    double res = 0;
    for(int i = 0; i < N; ++i) {
        res = max(res, dp[i][K]);
    }
    cout << res << endl;
}

AOJ 2556 Integer in Integer

解法

やり方は色々あると思うんですが,僕は桁DPっぽくやりました(正直実装方針かなりミスっていると思う.)

まず,[0..N] までの答え f(N) を求めることができれば,f(B) - f(A - 1) で求められるので,f(N) を考えましょう.

dp[i][j][k] := i 桁目まで見たときに,C と先頭 j 桁が一致していて,その桁まで見たときに N 以下であるかが k であるような,leading zero を除いた場合の数
dp0[i][j][k] := i 桁目まで見たときに,C と先頭 j 桁が一致していて,その桁まで見たときに N 以下であるかが k であるような,leading zero である場合の数

dp2[i][j][k] := i 桁目まで見たときに,N 以下であるかが j であり,leading zero であるかが k であるようなもので,含まれる C の数

まず dp を計算する前に,C と先頭 j 桁が一致しているときに d (1桁の数)を付け加えたら何桁一致することになるかを match[j][d] として求めておきます(これは全探索で求められる).

match[j][d] をつかって各 dp テーブルを更新していくのですが,流れとしては

  • dp2 で,1桁付け加えていく
  • dp と dp0 を更新する
  • i 桁目で dp と dp0 で C と完全一致するパターンを dp2 に付け加える

という感じになります.コードはごちゃごちゃしてるけど遷移は自然.
dp と dp0 は,あくまで i 桁目までみて先頭が C と一致しているような数を求めているだけにすぎないです(i + 1 桁目以降は存在しないとして数えている).
(例.C = 11 なら,3桁目までみて 11x となるのは 10 通り,2桁目まででは 11 の 1 通り.x の値によって,dp に入るか dp2 に入るかが変わってくる)

答えは dp2[i][0][0] + (i != |N| ? dp2[i][1][0] : 0) の総和です.
C = 0 のときはちょっと注意が必要です.

書いてて自分でもよくわからなくなってきた(???)

オーダーは O(2 * 10 * |C| * |A|) の 10^8 ぐらいで実際はもうちょい遅いかな~ぐらいです.

ソースコード

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

using ll = long long;

constexpr int M = 1e9 + 7;

int solve(string A, string C) {
    int const n = A.size();
    int const m = C.size();
    reverse(A.begin(), A.end());
    reverse(C.begin(), C.end());
    vector<vector<int>> match(m + 1, vector<int>(10));
    for(int d = 0; d <= 9; ++d) {
        char ch = '0' + d;
        for(int i = 0; i <= m; ++i) {
            int len = 0;
            for(int j = min(i + 1, m); j >= 1; --j) {
                if(C.substr(0, j) == C.substr(i - j + 1, j - 1) + ch) {
                    len = j;
                    break;
                }
            }
            match[i][d] = len;
        }
    }
    vector<vector<int>> dp(m + 1, vector<int>(2));
    vector<vector<int>> dp0(m + 1, vector<int>(2)); // leading zero
    vector<vector<vector<int>>> dp2(n + 1, vector<vector<int>>(2, vector<int>(2)));
    dp[0][0] = 1;
    int res = 0;
    for(int i = 0; i < n; ++i) {
        vector<vector<int>> nxt(m + 1, vector<int>(2)), nxt0(m + 1, vector<int>(2));
        for(int k = 0; k < 2; ++k) {
            for(int d = 0; d <= 9; ++d) {
                char ch = '0' + d;
                bool nk = k && ch == A[i] || ch > A[i];
                if(d == 0) {
                    (dp2[i + 1][nk][1] += dp2[i][k][0]) %= M;
                    (dp2[i + 1][nk][1] += dp2[i][k][1]) %= M;
                } else {
                    (dp2[i + 1][nk][0] += dp2[i][k][0]) %= M;
                    (dp2[i + 1][nk][0] += dp2[i][k][1]) %= M;
                }
            }
        }
        for(int j = 0; j <= m; ++j) {
            for(int k = 0; k < 2; ++k) {
                for(int d = 0; d <= 9; ++d) {
                    char ch = '0' + d;
                    bool nk = k && ch == A[i] || ch > A[i];
                    int nj = match[j][d];
                    if(d == 0) {
                        (nxt0[nj][nk] += dp[j][k]) %= M;
                        (nxt0[nj][nk] += dp0[j][k]) %= M;
                    } else {
                        (nxt[nj][nk] += dp[j][k]) %= M;
                        (nxt[nj][nk] += dp0[j][k]) %= M;
                    }
                }
            }
        }
        (dp2[i + 1][0][0] += nxt[m][0]) %= M;
        (dp2[i + 1][0][1] += nxt0[m][0]) %= M;
        (dp2[i + 1][1][0] += nxt[m][1]) %= M;
        (dp2[i + 1][1][1] += nxt0[m][1]) %= M;
        dp = move(nxt);
        dp0 = move(nxt0);
    }

    for(int i = 1; i <= n; ++i) {
        (res += dp2[i][0][0]) %= M;
        if(i != n) {
            (res += dp2[i][1][0]) %= M;
        }
    }
    if(C == "0") {
        (res += 1) % M;
    }
    return res;
}

string decrement(string A) {
    for(int i = A.size() - 1; i >= 0; --i) {
        if(A[i] > '0') {
            A[i]--;
            break;
        } else {
            A[i] = '9';
        }
    }
    if(A[0] == '0' && A.size() > 1) {
        return A.substr(1);
    } else {
        return A;
    }
}

int main() {
    string A, B, C;
    cin >> A >> B >> C;
    if(A != "0") {
        cout << (solve(B, C) - solve(decrement(A), C) + M) % M << endl;
    } else {
        cout << solve(B, C) << endl;
    }
}

感想

通ったからいいけど,桁DPっぽくやったのは失敗だったかなあと思っています.
今真っ白な状態から書くなら,j 桁目が C であるような N 以下の数っていうのを求めていく感じになりそう.[X] ... C ... [Y] の X と Y を N を越えないように数えるみたいな.

AOJ 2614 Almost Same Substring

解法

ローリングハッシュでやります.

同じ長さの文字列 S, T に対して,「左からみて何文字一致しているか」と「右からみて何文字一致しているか」をそれぞれ二分探索で求めます.
もし S と T が一文字だけ異なるなら,左からの一致文字数と右からの一致文字数の和が,長さから1だけ小さいものになっているはずです.

よって,問題で与えられた S, T' について,S[a..a+|T'|-1] と T' に対し,先程やったことをすれば求まります.

ソースコード

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

// 1-indexed
// s[1...n]
class rolling_hash {
public:
    rolling_hash(std::string const& s)
        : n(s.size()),
          mod({999999937LL, 1000000007LL})
    {
        for(int i = 0; i < 2; ++i) {
            hs[i].resize(n + 1);
            p[i].resize(n + 1);
            hs[i][0] = 0;
            p[i][0] = 1;
            for(int j = 0; j < n; ++j) {
                hs[i][j + 1] = (hs[i][j] * base + s[j]) % mod[i];
                p[i][j + 1] = p[i][j] * base % mod[i];
            }
        }
    }

    // s[i...]
    long long query(int idx, int i) const {
        return hs[i][idx];
    }
    // s[l + 1...r]
    long long query(int l, int r, int i) const {
        return ((hs[i][r] - hs[i][l] * p[i][r - l]) % mod[i] + mod[i]) % mod[i];
    }

private:
    int n;
    std::vector<long long> hs[2];
    std::vector<long long> p[2];

    const std::array<long long, 2> mod;
    static const long long base = 29LL;
};

int main() {
    string S, T;
    cin >> S >> T;
    rolling_hash hs1(S), hs2(T);
    int res = 0;
    for(int i = 1; i + T.size() - 1 <= S.size(); ++i) {
        int lb = i - 1, ub = i + T.size();
        while(ub - lb > 1) {
            int m = (ub + lb) / 2;
            if(hs1.query(i - 1, m, 1) == hs2.query(0, m - i + 1, 1)) {
                lb = m;
            } else {
                ub = m;
            }
        }
        int l = lb;
        lb = i - 1, ub = i + T.size();
        while(ub - lb > 1) {
            int m = (ub + lb) / 2;
            if(hs1.query(m - 1, i + T.size() - 1, 1) == hs2.query(m - i, T.size(), 1)) {
                ub = m;
            } else {
                lb = m;
            }
        }
        int r = ub;

        res += l + 2 == r;
    }
    cout << res << endl;
}

感想

ローリングハッシュ使う時いっつもインデックスで混乱する.