Official

F - Vacation Query Editorial by en_translator


This problem requires a data structure that supports “query that updates the values in a segment” and “query that retrieve values for a segment.”
In writer’s solution, a data structure called lazy segment tree is used to process such queries. Lazy segment tree is implemented in AtCoder Library; once you understand the usage, the implementation is handy.
Explaining a lazy segment tree is a bit troublesome, so we skip it here. If you want to know more about a lazy segment tree, please refer to other articles and exercises. See also: reference, ARMERIA’s article (Japanese), segment-query exercises in AOJ (Aizu Online Judge).

Here, we describe the monoid on the lazy segment tree.
First, for simplicity, we assume that only queries with \(c=2\) are given; that is, consider the case where we only want the maximum consecutive occurrences of 1 in an segment.
We denote by \(S(a, b)\) the substring from the \(a\)-th through \((b-1)\)-th characters of \(S\). Also, let the sought value be denoted by

  • \(m(a, b)\): maximum consecutive occurrences of 1 in the segment.

Now we want to choose the information to store, so that

  • “information on \(S(a, b)\)\(+\) “information on \(S(b, c)\)\(=\) “information on \(S(a, c)\);”
  • “information on \(S(a, b)\)” contains the value \(m(a, b)\).

(Just storing \(m\) is insufficient, as it does not satisfy the first criteria.)
In fact, we can always compute \(m\) by storing not only \(m(a, b)\) but also the following three values:

  • \(l(a, b)\): the number of consecutive 1s from the leftmost end to the right
  • \(r(a, b)\): the number of consecutive 1s from the rightmost end to the left
  • \(s(a, b)\): the number of characters in the segment (i.e. \(b-a\))

If we store these parameters as the monoid, the information on \(S(a, c)\) can be computed based on that for \(S(a, b)\) and \(S(b, c)\) can be derived as follows:

  • \(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)\)

Therefore, by storing these four parameters on the segment tree, each query can be processed in an \(\mathrm{O}(\log N)\) time, provided that only queries with \(c=2\) are given.

We now consider the case where those with \(c=1\) are also given. Such queries flips 0 and 1, so perhaps we need to consider \(m\), \(l\), and \(r\) with 1 replaced with 0. So let us define \(m_0\), \(l_0\), and \(r_0\) as

  • \(m_0(a, b)\): the number of the maximum consecutive occurrences of 0
  • \(l_0(a, b)\): the number of consecutive 0s from the leftmost end to the right
  • \(r_0(a, b)\): the number of consecutive 0s from the rightmost end to the left;

together with the former four parameters, store a total of seven on the segment tree, and the segment update queries can be properly processed too. (For more details, see also the sample code.) Therefore, the problem can be solved in a total of \(\mathrm{O}(N + Q \log N)\) time, which is fast enough.

  • Sample code (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: