E - 交互に並べられる区間 / Intervals That Can Be Arranged Alternately Editorial by admin
gpt-5.5-xhigh概要
各質問 \([L, R]\) について、その中に含まれる連続区間のうち、白黒を交互に並べ替えられる区間の個数を求めます。
重要なのは、区間内の白石数と黒石数の差が \(1\) 以下なら交互に並べられる、という点です。
考察
区間に含まれる白石の数を \(W\)、黒石の数を \(B\) とします。
この区間の石を並べ替えて、隣り合う石の色が必ず異なるようにできる条件は、
\[ |W - B| \leq 1 \]
です。
例えば、
WBWBのように白黒同数なら可能WBWのように片方が \(1\) 個だけ多い場合も可能WWWBのように差が \(2\) 以上あると、どう並べても同じ色が隣り合ってしまう
したがって、各区間について白石数と黒石数の差だけ分かればよいです。
ここで、文字列 \(S\) の各文字を次のように数値化します。
Wを \(+1\)Bを \(-1\)
そして累積和を
\[ A_i = S_1 + S_2 + \cdots + S_i \]
とします。
ただし、\(A_0 = 0\) とします。
区間 \([l, r]\) における白石数と黒石数の差は、
\[ A_r - A_{l-1} \]
で求められます。
よって、区間 \([l, r]\) がよい区間である条件は、
\[ |A_r - A_{l-1}| \leq 1 \]
です。
質問 \([L, R]\) で数えたいのは、
\[ L \leq l \leq r \leq R \]
を満たす区間 \([l, r]\) です。
これは、累積和の添字で見ると、
\[ L-1 \leq i < j \leq R \]
を満たす組 \((i, j)\) に対応します。
つまり、各質問は
累積和列 \(A\) の区間 \([L-1, R]\) に含まれる要素のペア \((i, j)\) のうち、値の差が \(1\) 以下のものの個数を求める問題
に変換できます。
素朴に各質問ごとにすべての区間を調べると、最悪で \(O(N^2Q)\) かかり、制約 \(N, Q \leq 5 \times 10^4\) では間に合いません。
そこで、区間に対するペア数を高速に更新できる Mo’s algorithm を使います。
アルゴリズム
まず、累積和配列 \(A\) を作ります。
W -> +1
B -> -1
として、\(A_0, A_1, \dots, A_N\) を計算します。
質問 \([L, R]\) は、累積和配列上の区間
\[ [L-1, R] \]
に変換します。
この区間に含まれる累積和の値の集合について、値の差が \(0\) または \(1\) のペア数を数えれば答えです。
Mo’s algorithm では、現在見ている区間を少しずつ左右に伸ばしたり縮めたりしながら、各質問に答えます。
現在の区間に含まれる累積和の値の頻度を freq で管理します。
ある値 \(x\) を新しく追加するとき、その \(x\) とペアを作れる既存の値は
\[ x-1, x, x+1 \]
です。
したがって、追加によって増える答えは
\[ freq[x-1] + freq[x] + freq[x+1] \]
です。
その後、freq[x] を \(1\) 増やします。
削除するときは逆に、先に freq[x] を \(1\) 減らしてから、
\[ freq[x-1] + freq[x] + freq[x+1] \]
を答えから引きます。
例えば、現在の区間に累積和の値が
1, 2, 2, 4
とあるとします。
ここに \(x = 3\) を追加する場合、差が \(1\) 以下になる既存の値は
2, 2, 4
なので、新たに \(3\) 個のよいペアが増えます。
これは
\[ freq[2] + freq[3] + freq[4] \]
で求められます。
計算量
- 時間計算量: \(O((N + Q)\sqrt{N} + Q \log Q)\)
- 空間計算量: \(O(N + Q)\)
実装のポイント
累積和 \(A_i\) は負になる可能性があります。
そのまま配列の添字として使うことはできないので、コードでは offset = N + 2 を足して、すべて正の値になるようにしています。
A[0] = offset;
とし、その後に +1 または -1 を加えて累積和を作っています。
また、質問 \([L, R]\) は累積和上では \([L-1, R]\) になるため、入力された \(L\) から \(1\) を引いて保存しています。
queries[i] = {L - 1, R, i};
答えはペア数なので、最大で \(O(N^2)\) になる可能性があります。
そのため、int ではなく long long を使う必要があります。
ソースコード
#include <bits/stdc++.h>
using namespace std;
struct Query {
int l, r, idx;
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N, Q;
cin >> N >> Q;
string S;
cin >> S;
int M = N + 1;
int offset = N + 2;
vector<int> A(M);
A[0] = offset;
for (int i = 1; i <= N; i++) {
int v = (S[i - 1] == 'W') ? 1 : -1;
A[i] = A[i - 1] + v;
}
vector<Query> queries(Q);
for (int i = 0; i < Q; i++) {
int L, R;
cin >> L >> R;
queries[i] = {L - 1, R, i};
}
int block = max(1, (int)sqrt(M));
sort(queries.begin(), queries.end(), [&](const Query& a, const Query& b) {
int ab = a.l / block;
int bb = b.l / block;
if (ab != bb) return ab < bb;
if (ab & 1) return a.r > b.r;
return a.r < b.r;
});
vector<int> freq(2 * N + 10, 0);
vector<long long> ans(Q);
long long curAns = 0;
int curL = 0, curR = -1;
auto add = [&](int idx) {
int x = A[idx];
curAns += (long long)freq[x - 1] + freq[x] + freq[x + 1];
freq[x]++;
};
auto remove = [&](int idx) {
int x = A[idx];
freq[x]--;
curAns -= (long long)freq[x - 1] + freq[x] + freq[x + 1];
};
for (const auto& q : queries) {
while (curL > q.l) add(--curL);
while (curR < q.r) add(++curR);
while (curL < q.l) remove(curL++);
while (curR > q.r) remove(curR--);
ans[q.idx] = curAns;
}
for (int i = 0; i < Q; i++) {
cout << ans[i] << '\n';
}
return 0;
}
この解説は gpt-5.5-xhigh によって生成されました。
posted:
last update: