题解:洛谷 AT_abc425_c Rotate and Sum Query

【题目来源】

洛谷:AT_abc425_c [ABC425C] Rotate and Sum Query - 洛谷

【题目描述】

给你一个长度为 \(N\) 的整数序列 \(A=(A_1,A_2,\ldots,A_N)\)

现在需要按顺序处理 \(Q\) 个查询。查询有两种类型,格式如下:

  • 1 c:将序列 \(A\) 的第一个元素移动到序列末尾,重复 \(c\) 次。
  • 2 l r:输出 \(\displaystyle \sum_{i=l}^r A_i\) 的值。

【输入】

输入从标准输入给出,格式如下:

\(N\) \(Q\) \(A_1\) \(A_2\) \(\ldots\) \(A_N\) \(\text{query}_1\) \(\text{query}_2\)
\(\vdots\)
\(\text{query}_Q\)

每个查询 \(\text{query}_i\) 有如下两种格式之一:

\(1\) \(c\)

\(2\) \(l\) \(r\)

【输出】

对于每个第二种类型的查询,按照出现顺序输出查询结果,每个结果占一行。

【输入样例】

4 3
3 1 4 5
2 1 3
1 1
2 2 3

【输出样例】

8
9

【核心思想】

  1. 问题分析:给定长度为 \(N\) 的序列 \(A\),支持两种操作:将首元素移到末尾 \(c\) 次(循环左移),以及查询区间 \([l, r]\) 的和。这是一个循环数组 + 前缀和问题,关键在于将循环移位转化为偏移量,利用数组复制技巧将循环结构线性化,从而用前缀和 \(O(1)\) 回答区间查询。

  2. 算法选择

    • 数组复制线性化:将原数组 \(A[1..N]\) 复制一份接在末尾,形成 \(A[1..2N]\),使任意循环区间都能映射为连续子数组
    • 偏移量维护:用 cnt 记录累计循环左移次数,实际查询位置为 \(l + cnt\)\(r + cnt\)
    • 前缀和查询:对扩展后的数组建前缀和 \(sa\),区间和 = \(sa[r] - sa[l-1]\)
  3. 关键步骤

    • 读取输入:读入 \(N, Q\) 和数组 \(A[1..N]\)
    • 数组扩展\(A[i+N] = A[i]\)\(i\)\(1\)\(N\)),实现循环数组的线性表示
    • 前缀和初始化\(sa[i] = sa[i-1] + A[i]\)\(i\)\(1\)\(2N\)
    • 处理查询
      • 类型 \(1\ c\)cnt = (cnt + c) % N,更新循环偏移量
      • 类型 \(2\ l\ r\):实际区间变为 \([l+cnt,\ r+cnt]\),答案 = \(sa[r+cnt] - sa[l+cnt-1]\)
    • 输出答案
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N + Q)\),预处理 \(O(N)\),每次查询 \(O(1)\)
    • 空间复杂度:\(O(N)\),扩展数组和前缀和各需 \(2N\) 空间
  5. 循环数组线性化的核心思想

    • 空间换时间:通过复制数组将循环结构"拉直",使任意循环区间都能表示为普通连续区间
    • 偏移量抽象:将多次循环移位抽象为单个偏移量 cnt,避免每次实际移动元素
    • 模运算保界cnt % N 保证偏移量始终在 \([0, N-1]\) 范围内,防止数组越界
    • 适用于"循环移位 + 区间查询"类问题,核心是维护虚拟偏移而非物理移动

【算法标签】

普及- #前缀和

【代码详解】

#include <bits/stdc++.h>
using namespace std;

#define int long long          // 使用长整型防止数据溢出
const int N = 200005 * 2;     // 定义数组最大长度(扩展为两倍)

int n;                        // 原始数组长度
int q;                        // 查询次数
int cnt;                      // 循环偏移计数器
int a[N];                     // 原始数组(扩展为两倍实现循环)
int sa[N];                    // 前缀和数组

signed main()
{
    // 输入数组长度和查询次数
    cin >> n >> q;

    // 输入原始数组并扩展为两倍长度(实现循环数组)
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        a[i + n] = a[i];      // 复制一份实现循环数组
    }

    // 计算前缀和数组(sa[i]表示前i个元素的和)
    for (int i = 1; i <= 2 * n; i++)
    {
        sa[i] = sa[i - 1] + a[i];
    }

    // 处理每个查询
    while (q--)
    {
        int op, c, l, r;
        cin >> op;            // 查询类型(1或2)

        if (op == 1)          // 类型1:循环偏移操作
        {
            cin >> c;
            cnt = (cnt + c) % n;  // 更新偏移量(取模保证在数组范围内)
        }
        else                  // 类型2:区间求和查询
        {
            cin >> l >> r;
            // 计算偏移后的实际位置
            l = l + cnt;
            r = r + cnt;
            // 使用前缀和数组快速计算区间和
            cout << sa[r] - sa[l - 1] << endl;
        }
    }

    return 0;
}

【运行结果】

4 3
3 1 4 5
2 1 3
8
1 1
2 2 3
9
posted @ 2026-08-27 11:02  团爸讲算法  阅读(5)  评论(0)    收藏  举报