Official

I - Vacation Query Editorial by Nyaan


この問題は「区間の値を更新するクエリ」「区間の値を取得するクエリ」を処理できるデータ構造が必要です。
こうしたクエリを処理するために、想定解では 遅延セグメント木 と呼ばれるデータ構造を利用しています。遅延セグメント木は AtCoder Library で実装されているデータ構造で、使い方を理解すれば容易に実装することが出来ます。
遅延セグメント木に関する説明は煩雑になるのでここでは割愛します。遅延セグメント木について知りたい方は他の記事や練習問題を参考にしてください。参考:リファレンス, アルメリアさんの記事, AOJ の区間クエリ練習問題集

以下では遅延セグメント木に載せるモノイドを説明します。
まず、簡単のためクエリが \(c=2\) のみである場合を考えます。 つまり、「区間に含まれる連続する 1 の個数の最大値」のみを求めたい場合を考えます。
\(S\)\(a\) 文字目から \(b-1\) 文字目を取り出した部分列を \(S(a, b)\) とします。また、クエリで求めたいものを

  • \(m(a, b)\) : 区間に含まれる連続する 1 の個数の最大値

と表します。このとき

  • \(S(a, b)\) に関する情報」\(+\)\(S(b, c)\) に関する情報」\(=\)\(S(a, c)\) に関する情報」
  • \(S(a, b)\) に関する情報」には \(m(a, b)\) の値が含まれている

となるように上手く載せる情報を選びたいです。(載せる情報として \(m\) を載せるだけでは \(1\) 番目の条件を満たさないためうまくいきません。)
うまく \(m\) を計算できるように載せる情報を決めると、\(m(a, b)\) に加えて次の \(3\) つを追加して載せればよいことがわかります。

  • \(l(a, b)\) : 左端から右に向けて伸びる連続する 1 の個数
  • \(r(a, b)\) : 右端から左に向けて伸びる連続する 1 の個数
  • \(s(a, b)\) : 区間の文字数 (言い換えると, \(b-a\) )

これらの情報をモノイドとして載せることにすると、\(S(a, b)\)\(S(b, c)\) の情報から \(S(a, c)\) の情報は以下の式で求まります。

  • \(m(a, c) = \max(\lbrace m(a,b), m(b,c), r(a,b)+l(b,c) \rbrace)\)
  • \(l(a, c) = (s(a, b) + l(b, c) \text{ if } l(a, b) = s(a, b) \text{ else } l(a, b))\)
  • \(r(a, c) = (r(a, b) + s(b, c) \text{ if } r(b, c) = s(b, c) \text{ else } r(b, c))\)
  • \(s(a, c) = s(a, b) + s(b, c)\)

よって、これら 4 種類の情報をセグメント木に載せることで \(c=2\) のクエリのみが与えられる場合をクエリあたり \(\mathrm{O}(\log N)\) で処理することができるようになります。

\(c=1\) のクエリも与えられる場合を考えます。\(c=1\) のクエリでは 01 が flip するため、\(m,l,r\)10 に置き換えたものを考える必要がありそうです。そこで、\(m_0, l_0, r_0\)

  • \(m_0(a, b)\) : 区間に含まれる連続する 0 の個数の最大値
  • \(l_0(a, b)\) : 左端から右に向けて伸びる連続する 0 の個数
  • \(r_0(a, b)\) : 右端から左に向けて伸びる連続する 0 の個数

と置いて, 先の \(4\) 種類の情報と合わせて全部で \(7\) 種類の情報を遅延セグメント木に載せると上手く区間更新クエリを処理できることが分かります。(詳細は実装例を参考にしてください。) 以上より \(\mathrm{O}(N + Q \log N)\) でこの問題を解くことが出来て、十分高速です。

  • 実装例(C++)
#include <iostream>
#include <string>
using namespace std;

#include "atcoder/lazysegtree.hpp"

#define rep(i, n) for (int i = 0; i < (n); i++)

struct Data {
  int mx[2], l[2], r[2], len;

  Data() {
    rep(t, 2) mx[t] = l[t] = r[t] = 0;
    len = 0;
  }
  Data(char c) {
    rep(t, 2) mx[t] = l[t] = r[t] = (c == '0' + t);
    len = 1;
  }

  Data flip() const {
    Data res = *this;
    swap(res.mx[0], res.mx[1]);
    swap(res.l[0], res.l[1]);
    swap(res.r[0], res.r[1]);
    return res;
  }

  static Data merge(const Data& l, const Data& r) {
    Data res;
    rep(t, 2) {
      res.l[t] = l.l[t] + (l.l[t] == l.len ? r.l[t] : 0);
      res.r[t] = r.r[t] + (r.r[t] == r.len ? l.r[t] : 0);
      res.mx[t] = max({l.mx[t], r.mx[t], l.r[t] + r.l[t]});
    }
    res.len = l.len + r.len;
    return res;
  }
};

Data f(Data a, Data b) { return Data::merge(a, b); }
Data g(int a, Data b) { return a ? b.flip() : b; }
int h(int a, int b) { return a ^ b; }
Data ti() { return Data(); }
int ei() { return 0; }

int main() {
  cin.tie(nullptr);
  ios::sync_with_stdio(false);

  int N, Q;
  string S;
  cin >> N >> Q >> S;

  vector<Data> init(N);
  rep(i, N) init[i] = Data(S[i]);
  atcoder::lazy_segtree<Data, f, ti, int, g, h, ei> seg(init);

  while (Q--) {
    int c, l, r;
    cin >> c >> l >> r;
    --l;
    if (c == 1) {
      seg.apply(l, r, 1);
    } else {
      auto ans = seg.prod(l, r);
      cout << ans.mx[1] << endl;
    }
  }
}

posted:
last update: