题解:洛谷 AT_abc465_c Reverse Permutation

【题目来源】

洛谷:AT_abc465_c [ABC465C] Reverse Permutation

【题目描述】

给定一个整数 \(N\) 和一个长度为 \(N\) 的字符串 \(S\),字符串 \(S\) 仅包含 ox

有一个长度为 \(N\) 的整数序列 \(A=(A_1,A_2,\ldots,A_N)\)。最初,\(A=(1,2,\ldots,N)\)

你需要对 \(A\) 依次进行如下操作(从 \(k=1\)\(k=N\)):

  • 如果 \(S_k=\) o,则反转 \(A\) 的前 \(k\) 个元素。即,将 \(A\) 变为 \((A_k,A_{k-1},\ldots,A_1,A_{k+1},A_{k+2},\ldots,A_N)\)
  • 如果 \(S_k=\) x,则什么也不做。

请输出所有操作结束后 \(A\) 的最终状态。

【输入】

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

\(N\)
\(S\)

【输出】

输出所有操作完成后 \(A\) 的元素,空格分隔。

【输入样例】

5
ooxoo

【输出样例】

5 2 1 3 4

【核心思想】

  1. 问题分析:给定长度为 \(N\) 的序列 \(A=(1,2,\ldots,N)\) 和操作字符串 \(S\)o 表示反转前 \(k\) 个元素,x 表示不操作),按 \(k=1\)\(N\) 依次执行操作后输出最终序列。直接模拟每次反转前 \(k\) 个元素的时间复杂度为 \(O(N^2)\),需要优化。这是一个双端队列 + 奇偶性追踪问题,核心在于利用"反转的叠加效应"将 \(O(N^2)\) 的反转操作优化为 \(O(1)\) 的双端队列插入。

  2. 算法选择

    • 双端队列(Deque)+ 翻转次数奇偶性:用 flip_cnt 记录累计反转次数。反转偶数次等价于未反转,反转奇数次等价于反转一次。利用这一性质,在奇数反转次数时从队首插入(模拟反转后的尾部),偶数时从队尾插入
  3. 关键步骤

    • 读入数据:读取 \(N\) 和字符串 \(S\)
    • 初始化:双端队列 dq 为空,flip_cnt = 0
    • 遍历处理\(i\)\(0\)\(N-1\)):
      • flip_cnt % 2 == 0(当前方向正常):dq.push_back(i + 1)
      • 否则(当前方向已反转):dq.push_front(i + 1)
      • 若 $S_i = $ oflip_cnt++
    • 最终调整:若 flip_cnt % 2 == 1reverse(dq)(奇数次反转需要最终翻转)
    • 输出结果:依次输出 dq 中元素
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N)\),双端队列头尾插入均为 \(O(1)\),最终可能一次 \(O(N)\) 翻转
    • 空间复杂度:\(O(N)\),双端队列存储 \(N\) 个元素
  5. 双端队列与反转奇偶性的核心思想

    • 反转的叠加等价性:连续两次反转前 \(k\) 个元素会恢复原状,因此只需关心反转次数的奇偶性,而非具体次数。flip_cnt % 2 足以判断当前序列方向
    • 双端插入模拟反转:当序列处于"逻辑反转"状态(flip_cnt 为奇数)时,新元素 \(i+1\) 应被放到当前序列的"末尾"。但由于序列逻辑上已反转,物理上的队首对应逻辑上的队尾,因此用 push_front 实现 \(O(1)\) 的"逻辑尾部插入"
    • 惰性翻转策略:不立即执行物理反转,而是通过插入位置的选择延迟反转操作,只在最后根据总奇偶性决定是否执行一次物理翻转
    • 操作顺序的精确对应:第 \(k\) 步操作前,前 \(k-1\) 步的反转效果已通过 flip_cnt 的奇偶性体现在插入位置选择上,确保每一步都在正确的逻辑状态下执行
    • 适用于序列反转优化、状态叠加类问题,核心是将频繁的前缀反转转化为 \(O(1)\) 的双端操作

【算法标签】

普及- #队列

【代码详解】

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

int n;                             // n: 序列长度
string s;                         // s: 操作字符串,'o' 表示反转,'x' 表示不操作
deque<int> dq;                    // dq: 双端队列,用于高效地在两端插入元素
int flip_cnt;                     // flip_cnt: 记录 'o' 操作的次数(即需要反转的次数)

int main()
{
    cin >> n >> s;                // 读入序列长度和操作字符串

    for (int i = 0; i < n; i++)  // 遍历每个位置
    {
        if (flip_cnt % 2 == 0)    // 如果当前反转次数为偶数,序列方向正常
            dq.push_back(i + 1);  // 从尾部加入元素
        else                      // 如果当前反转次数为奇数,序列方向已反转
            dq.push_front(i + 1); // 从头部加入元素(等效于在反转后的尾部加入)

        if (s[i] == 'o')          // 如果当前操作是 'o'
            flip_cnt++;           // 反转次数加一
    }

    if (flip_cnt % 2)             // 如果最终反转次数为奇数,整体需要再反转一次
        reverse(dq.begin(), dq.end());

    for (int i = 0; i < dq.size(); i++)  // 输出最终序列
        cout << dq[i] << " ";

    cout << endl;

    return 0;
}

【运行结果】

5
ooxoo
5 2 1 3 4
posted @ 2026-08-26 10:13  团爸讲算法  阅读(9)  评论(0)    收藏  举报