题解: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

【核心思想】

  1. 问题分析:给定长度为 \(N\) 的字符串 \(S\)(由 WB 组成),定义一个区间 \([l, r]\)好区间,如果该区间内的石头可以重新排列使得相邻石头颜色不同。对于 \(Q\) 个询问 \([L_i, R_i]\),求每个询问范围内好区间的数量。这是一个莫队算法问题,关键在于将好区间的判定转化为前缀差分数组的性质。

  2. 算法选择

    • 前缀差分数组:定义 cumul[i] 为前 \(i\) 个石头中(白色数 - 黑色数)
    • 好区间判定:区间 \([l, r]\) 是好区间当且仅当 cumul[l-1]cumul[r] 的差值绝对值不超过 \(1\),即 \(|cumul[r] - cumul[l-1]| \leq 1\)
    • 莫队算法:离线处理区间询问,通过分块排序和指针移动高效统计答案
  3. 关键步骤

    • 计算前缀差分数组
      • cumul[0] = 0
      • cumul[i+1] = cumul[i] + (S[i] == 'W' ? 1 : -1)
    • 转化询问:询问 \([L, R]\) 对应 cumul 数组中的区间 \([L-1, R]\)
    • 莫队算法处理
      • 分块:块大小 \(B = \sqrt{N}\)
      • 排序:按左端点所在块号排序,同一块内按右端点排序
      • 指针移动:维护当前区间 \([curL, curR]\),通过 adddel 操作移动指针
      • 统计答案add(idx) 时,查询与 cumul[idx] 差值为 \(-1, 0, 1\) 的已存在元素个数,累加到 sum
    • 按原始顺序输出答案
  4. 时间/空间复杂度

    • 时间复杂度:\(O((N + Q) \cdot \sqrt{N})\),莫队算法标准复杂度
    • 空间复杂度:\(O(N)\),前缀差分数组、计数数组、询问数组
  5. 莫队算法的核心思想

    • 离线处理:先收集所有询问,排序后统一处理,避免重复计算
    • 分块排序:将左端点分块,同一块内按右端点排序,减少指针移动次数
    • 指针移动:通过 adddel 操作维护当前区间状态,每次移动 \(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
posted @ 2026-06-14 10:45  团爸讲算法  阅读(10)  评论(0)    收藏  举报