题解:AtCoder AT_awc0088_e Intervals That Can Be Arranged Alternately
【题目来源】
AtCoder:E - Intervals That Can Be Arranged Alternately
【题目描述】
Takahashi has arranged \(N\) white and black stones in a row.
The color of the \(i\)-th stone from the left is represented by the \(i\)-th character of the string \(S\), where W corresponds to a white stone and B corresponds to a black stone.
A contiguous interval \([l, r]\) (\(1 \leq l \leq r \leq N\)) refers to the sequence of stones from the \(l\)-th to the \(r\)-th from the left.
Consider taking out all the stones in a contiguous interval and rearranging them in a row in any order you like. If there exists an arrangement such that every two adjacent stones have different colors, we call that contiguous interval a good interval.
In particular, a contiguous interval of length \(1\) is always a good interval.
Aoki asks \(Q\) questions.
In the \(i\)-th question, integers \(L_i, R_i\) are given.
For each question, find the number of good intervals among the contiguous intervals \([l, r]\) satisfying \(L_i \leq l \leq r \leq R_i\).
高桥将 \(N\) 颗白色和黑色的石头排成一行。
从左数第 \(i\) 颗石头的颜色由字符串 \(S\) 的第 \(i\) 个字符表示,其中 W 对应白色石头,B 对应黑色石头。
一个连续区间 \([l, r]\)(\(1 \leq l \leq r \leq N\))指从左数第 \(l\) 颗到第 \(r\) 颗石头的序列。
考虑取出一个连续区间中的所有石头,并以任意顺序将它们重新排成一行。如果存在一种排列方式使得任意相邻两颗石头的颜色都不同,则称该连续区间为好区间。
特别地,长度为 \(1\) 的连续区间总是好区间。
青木问了 \(Q\) 个问题。
在第 \(i\) 个问题中,给出整数 \(L_i, R_i\)。
对于每个问题,求所有满足 \(L_i \leq l \leq r \leq R_i\) 的连续区间 \([l, r]\) 中好区间的数量。
【输入】
\(N\) \(Q\)
\(S\)
\(L_1\) \(R_1\)
\(L_2\) \(R_2\)
\(\vdots\)
\(L_Q\) \(R_Q\)
The first line contains the number of stones \(N\) and the number of questions \(Q\), separated by a space.
The second line contains a string \(S\) of length \(N\) representing the color of each stone.
The \(i\)-th of the following \(Q\) lines contains the integers \(L_i, R_i\) representing the \(i\)-th question, separated by a space.
【输出】
Print \(Q\) lines.
The \(i\)-th line should contain the number of good intervals for the \(i\)-th question.
【输入样例】
5 4
WBWBW
1 5
2 4
1 1
3 5
【输出样例】
15
6
1
6
【核心思想】
-
问题分析:给定长度为 \(N\) 的字符串 \(S\)(由
W和B组成),定义一个区间 \([l, r]\) 为好区间,如果该区间内的石头可以重新排列使得相邻石头颜色不同。对于 \(Q\) 个询问 \([L_i, R_i]\),求每个询问范围内好区间的数量。这是一个莫队算法问题,关键在于将好区间的判定转化为前缀差分数组的性质。 -
算法选择:
- 前缀差分数组:定义
cumul[i]为前 \(i\) 个石头中(白色数 - 黑色数) - 好区间判定:区间 \([l, r]\) 是好区间当且仅当
cumul[l-1]和cumul[r]的差值绝对值不超过 \(1\),即 \(|cumul[r] - cumul[l-1]| \leq 1\) - 莫队算法:离线处理区间询问,通过分块排序和指针移动高效统计答案
- 前缀差分数组:定义
-
关键步骤:
- 计算前缀差分数组:
cumul[0] = 0cumul[i+1] = cumul[i] + (S[i] == 'W' ? 1 : -1)
- 转化询问:询问 \([L, R]\) 对应
cumul数组中的区间 \([L-1, R]\) - 莫队算法处理:
- 分块:块大小 \(B = \sqrt{N}\)
- 排序:按左端点所在块号排序,同一块内按右端点排序
- 指针移动:维护当前区间 \([curL, curR]\),通过
add和del操作移动指针 - 统计答案:
add(idx)时,查询与cumul[idx]差值为 \(-1, 0, 1\) 的已存在元素个数,累加到sum
- 按原始顺序输出答案
- 计算前缀差分数组:
-
时间/空间复杂度:
- 时间复杂度:\(O((N + Q) \cdot \sqrt{N})\),莫队算法标准复杂度
- 空间复杂度:\(O(N)\),前缀差分数组、计数数组、询问数组
-
莫队算法的核心思想:
- 离线处理:先收集所有询问,排序后统一处理,避免重复计算
- 分块排序:将左端点分块,同一块内按右端点排序,减少指针移动次数
- 指针移动:通过
add和del操作维护当前区间状态,每次移动 \(O(1)\) - 好区间转化:利用前缀差分数组,将区间判定转化为两个位置的值比较
- 适用于离线区间统计、区间计数、区间查询类问题
【算法标签】
莫队
【代码详解】
#include <bits/stdc++.h>
using namespace std;
// 定义长整型别名,便于处理大数据
#define int long long
// 定义数组最大容量
const int N = 200005;
// 全局变量声明
int n, m, B; // n: 石头数量, m: 询问次数, B: 分块大小
int cumul[N]; // 前缀差分数组,cumul[i]表示前i个石头中白减黑的数量
int cnt[N * 2]; // 值域计数(带偏移量N,因为cumul值范围为[-n, n])
int sum; // 当前答案,表示满足条件的数对数量
// 询问结构体
struct Query
{
int l, r; // 区间左右端点
int id; // 询问编号
} q[N]; // 询问数组
int ans[N]; // 存储每个询问的答案
// 添加位置idx(将cumul[idx]加入当前区间)
void add(int idx)
{
int c = cumul[idx] + n; // 计算带偏移量的索引
// 新增的数可以与已有的差值为-1、0、1的数组成数对
sum += cnt[c - 1] + cnt[c] + cnt[c + 1];
cnt[c]++; // 增加该值的计数
}
// 移除位置idx(将cumul[idx]移出当前区间)
void del(int idx)
{
int c = cumul[idx] + n; // 计算带偏移量的索引
cnt[c]--; // 减少该值的计数
// 移除的数之前与差值为-1、0、1的数组成的数对也要减去
sum -= cnt[c - 1] + cnt[c] + cnt[c + 1];
}
// 排序比较函数(普通莫队排序,无奇偶优化)
bool cmp(Query a, Query b)
{
if (a.l / B != b.l / B)
return a.l < b.l; // 不同块按左端点升序
return a.r < b.r; // 同块按右端点升序
}
// 主函数入口
signed main()
{
// 关闭同步提高IO效率
ios::sync_with_stdio(false);
cin.tie(nullptr);
// 读取石头数量和询问次数
cin >> n >> m;
string S;
cin >> S;
// 读入所有询问(下标从1开始)
for (int i = 1; i <= m; i++)
{
int L, R;
cin >> L >> R;
q[i].l = L - 1; // 询问[L,R]对应cumul[L-1..R]
q[i].r = R;
q[i].id = i; // 记录原始顺序
}
// 计算前缀差分数组(下标从0到n)
// cumul[i] = 前i个石头中白色数量减去黑色数量
cumul[0] = 0;
for (int i = 0; i < n; i++)
{
cumul[i + 1] = cumul[i] + (S[i] == 'W' ? 1 : -1);
}
// 计算分块大小
B = sqrt(n);
// 按照莫队顺序排序询问(从1开始)
sort(q + 1, q + m + 1, cmp);
// 初始化计数器和答案
memset(cnt, 0, sizeof(cnt));
sum = 0;
// 处理每个询问(初始区间为空,l=1, r=0)
for (int i = 1, l = 1, r = 0; i <= m; i++)
{
// 移动指针到目标区间
while (l > q[i].l) // 左边界向左扩展
add(--l);
while (r < q[i].r) // 右边界向右扩展
add(++r);
while (l < q[i].l) // 左边界向右收缩
del(l++);
while (r > q[i].r) // 右边界向左收缩
del(r--);
// 存储当前询问的答案
ans[q[i].id] = sum;
}
// 按原始顺序输出所有答案
for (int i = 1; i <= m; i++)
{
cout << ans[i] << '\n';
}
return 0;
}
【运行结果】
5 4
WBWBW
1 5
2 4
1 1
3 5
15
6
1
6
浙公网安备 33010602011771号