[AGC063] B - Insert 1, 2, 3, ...
[Q] https://atcoder.jp/contests/agc063/tasks/agc063_b
考察
・生成可能なパターンの始まりはいつも"1"から。
・なので1をもとにグルーピングしていく。
問題文サンプルを考える。
10
1 2 1 1 2 1 3 4 2 3
cnt: 1 0 2 3 0 4 0 0 0 0
pos: 0 0 2 3 3 5 3 3 2 2
add: 1 1 2 3 3 4 3 3 2 2
pos: どの1と同じグループかを示す。
cnt: 値が1の時にだけ入れる。この1をとると、どれだけ全体の組み合わせ数が増えるかを示す。
add: この時点で解答にどれだけ追加したかを示す。
pos[val] = {index}とすると、この問題文サンプルは、次のようにグループわけできる。
pos[0] = {0, 1}
pos[2] = {2, 8, 9}
pos[3] = {3, 4, 6, 7}
pos[5] = {5}
・先頭から探索する。
A[i]を必ず使うとみなした場合、成立するパターン数を解答に加算する。
・A[i] = 1の場合を考える
{pair0}, {pair1}, 1
ときたら、このとき解答は+3される。
自分より前に、固まったグループがいくつあるかが分かればいい。
処理は、
1を{pair2}の先頭として
{pair0}, {pair1}, {pair2}
の集団とみなしていく。
・A[i] >= 2の場合を考える。
1.
{1, 2, 3} {1}, 2
の場合。
{1, 2, 3},{1, 2}として統合される。
このとき解答は+2される。
自分を含めたグループの数が分かればいい。
2.
{1, 2} {1, 2, 3, 4}, 3
の場合。
{1, 2, 1, 2, 3, 4, 3}
の1つのグループに統合する必要がある。
この時解答は+1される。
3がどの時点の1とグルーピングされるか探索する必要がある。
セグ木で求まる。
Q. なぜ俺は11WAもしたのか?
A. 問題文にあるサンプルを網羅できていないことが、5分前に判明した。
問題文サンプルを考える。
10
A[]:1 2 1 1 2 1 3 4 2 3
index:0 1 2 3 4 5 6 7 8 9
cnt: 1 0 2 3 0 4 0 0 0 0
pos: 0 0 2 3 3 5 3 3 2 2
add: 1 1 2 3 3 4 3 3 2 2
~~~ この、A[8] = 2 が、pos[8] = 2と結びついていることが大事。
A[8] = 2 について。
この2は、
A[2] = 1と結びつく必要がある。
しかし、
「最寄りの1と結びつける」というアルゴリズムにした場合、
A[5] = 1と結びついてしまう。
その場合、
A[3]以降を抜粋すると
1 2 1 3 4 2
a a b a a b
2つの連続部分列が絡み合っている数列とみなされて、NG文字列として扱ってしまう。
実際には正しい生成可能な文字列を得ることはできるはずなので、ペアリングの仕方が悪い。
どうやってA[5]とのマッチングを避けるか。
A[8]を探索しているときの、値1のindexリストは、
B[1] = {0, 2, 3, 5}
だが、ここでいくつかペア済みのindexがある。
A[0]はA[1]と{1,2}を形成しているので使用済み。
A[3]はA[4,6,7]と{1,2,3,4}を形成しているので使用済み。
そのため、
B[1] = {2, 5}
となっていてほしい。
indexの後ろからマッチングを試行する。
・index = 5のとき。
pos[5, 8)において、もっとも小さい値を見ると、
index: 5 6 7
pos: 5 3 3
minIndex = 3となっている。
これは、A[8] = 2を、A[5] = 1と結びつけた場合、
その中間にA[3] = 1と結びついているグループが存在していることになる。
これは、2つの文字列を入り組ませたNG文字列と判定する。
どうなればいいかというと、
index == minIndexが成立すれば、正しい生成可能な文字列といえる。
まだB[1]の候補はあるので、次を調べる。
・index = 2 のとき。
pos[2, 8)において、もっとも小さい値を見ると、
index: 2 3 4 5 6 7
2 3 3 5 3 3
minIndex = 2となっている。
これは、index == minIndexが成立するので、
index:0 1 2 3 4 5 6 7 8 9
A[]:1 2{1 1 2 1 3 4 2}3
[2, 8]のグルーピングを形成すればいい。
解答への加算は+2で、
{1 2}, {1 1 2 1 3 4 2} の2グループの取り方があるとみなせる。
cnt[2] = 2を加算すればいい。
実装
ll cnt0[500050]{}; // 値1の場所。この時点で完結している組み合わせ数
ll pos0[500050]{}; // どの値1と仲間か
ll add[500050]; // デバッグ用
vector<vector<ll>> B(500050, vector<ll>()); // B[val] = posindex
int main() {
cincout();
ll N;
cin >> N;
vector<ll> A(N);
rep(i, N) cin >> A[i];
RangeMin Z; // セグ木。範囲の最小indexを探す
Z.init(500050);
ll ans = 0; // 解答
ll precnt = 0; // 1手前に解答をいくつ増やしたか。
ll ng = -1; // 生成可能でない文字列が作られた場合、リセットする
rep(i, N) {
ll a = A[i];
if (a == 1) {
++precnt;
cnt0[i] = precnt;
pos0[i] = i;
ans += precnt;
Z.update(i, i); // id, val
} else {
auto failure = [&]() { // 生成可能でない数値が来た場合、リセットする
ng = i;
precnt = 0;
Z.update(i, -1);
};
// 生成可能な数値かどうかテスト
bool ok = true;
ll b = 0;
while (1) {
if (B[a - 1].empty()) { // グループの末端値が(a-1)であるグループの存在確認
ok = false;
break;
}
b = B[a - 1].back(); // グルーピングを試行するindex
if (b < ng) { // 生成可能でない数値をまたいでいるなら、生成不可
ok = false;
break;
}
B[a - 1].pop_back(); // 試行した値はもう使わないので除外
ll pid = Z.query(pos0[b], i); // 範囲内に、より手前とペアリングしているグループがあってはいけない
if (pid != pos0[b]) { // pid == pos0[b]のはず
continue; // 失敗なので、次のB[a-1]の値で再試行。
}
else break; // 成功
}
if (!ok){
failure();
continue;
}
Z.update(i, pos0[b]); // id, val
pos0[i] = pos0[b];
precnt = cnt0[pos0[b]];
ans += precnt;
}
add[i] = precnt;
B[a].push_back(i);
}
cout << ans << endl;
}
https://atcoder.jp/contests/agc063/submissions/44119608