Official

E - Duplicate Editorial by Nyaan


はじめに、\(S\) の中で 2 以上の数字が隣り合っている箇所があったら答えは -1 です。
そうでない場合を考えます。たとえば \(S=\) 1121131 とします。操作を行うごとに S は以下のように変化します。

  • 1121131
  • 111211113
  • 11112111111
  • 11111211111
  • 111111211111
    \(\vdots\)

列を観察すると次のような事実が分かります。

  • 末尾に連なる連続した同じ数字 (上の例での 3111111) は長さが 1 ずつ減っていきやがて無くなる。
  • 末尾でない箇所の 2 から 9 は個数が変化しない。
  • 末尾でない箇所の連続する 1 は, 直後の数字の値を \(x\) として \(x-1\) 個ずつ増えていく。 例えば先頭の 11 は, その直後の数字の値が \(2\) なので \(2-1=1\) 個ずつ増える。

この法則のポイントは「同じ数字が連続する列は末尾の要素しか減っていかない」という点です。これを利用することで、\(S\) を連長圧縮 (Run Length Encoding) した列を持ち、連長圧縮した列の末尾を pop しながら \(S\) の文字数・操作回数をシミュレーションするアルゴリズムで解くことができるとわかります。(詳細は実装例を参照ください)
計算量は \(\mathrm{O}(N)\) で十分高速です。

  • 実装例(C++)
#include <bits/stdc++.h>
using namespace std;

#include "atcoder/modint.hpp"
using mint = atcoder::modint998244353;

vector<pair<char, int>> RunLengthEncoding(string& S) {
  if (S.empty()) return {};
  vector<pair<char, int>> ret;
  char c = S[0];
  int n = 1;
  for (int i = 1; i < (int)S.size(); i++) {
    if (S[i] == c)
      n++;
    else {
      ret.emplace_back(c, n);
      c = S[i], n = 1;
    }
  }
  ret.emplace_back(c, n);
  return ret;
}

int main() {
  int N;
  string S;
  cin >> N >> S;
  for (int i = 0; i + 1 < N; i++) {
    if (S[i] >= '2' and S[i + 1] >= '2') {
      cout << "-1\n";
      exit(0);
    }
  }
  auto rle = RunLengthEncoding(S);
  mint t = 0;
  char last = '1';
  while (!rle.empty()) {
    char c;
    mint n;
    tie(c, n) = rle.back();
    rle.pop_back();
    if (c == '1') {
      n += t * (last - '1');
      t += n;
    } else {
      t += 1;
    }
    last = c;
  }
  cout << (t - 1).val() << "\n";
}

posted:
last update: