题解:洛谷 AT_abc470_c Inc, Dec, Xor

【题目来源】

洛谷:AT_abc470_c [ABC470C] Inc, Dec, Xor

【题目描述】

有一个长度为 \(N\) 的整数序列 \(A = (A_1, A_2, \dots, A_N)\)。初始时,\(A\) 的所有元素均为 \(0\)

按顺序处理 \(Q\) 个查询,查询有以下两种类型:

  • 1 x:将 \(A_x\) 的值增加 \(1\)
  • 2:对于所有 \(i = 1, 2, \dots, N\),如果 \(A_i \ge 1\),则将 \(A_i\) 的值减少 \(1\)

请输出每个查询处理完成后,\(A_1, A_2, \dots, A_N\) 的按位异或(XOR)值。


按位异或运算的定义

对于非负整数 \(A, B\),其按位异或 \(A \oplus B\) 定义如下:

  • \(A \oplus B\) 的二进制表示中,第 \(k\) 位(\(k \ge 0\))为 \(1\),当且仅当 \(A\)\(B\) 的第 \(k\) 位中恰好有一个为 \(1\);否则为 \(0\)

例如,\(3 \oplus 5 = 6\)(二进制:\(011 \oplus 101 = 110\))。

一般地,\(k\) 个非负整数 \(p_1, p_2, \dots, p_k\) 的按位异或定义为 \((\dots((p_1 \oplus p_2) \oplus p_3) \oplus \dots \oplus p_k)\),可以证明其结果与计算顺序无关。

【输入】

输入第一行包含两个整数 \(N, Q\)

接下来 \(Q\) 行,每行描述一个查询,格式为以下两种之一:

  • 1 x
  • 2

【输出】

输出 \(Q\) 行,第 \(i\) 行输出第 \(i\) 个查询处理完成后,\(A_1, A_2, \dots, A_N\) 的按位异或值。

【输入样例】

2 5
1 2
1 2
1 1
2
2

【输出样例】

1
2
3
1
0

【核心思想】

  1. 问题分析:给定长度为 \(N\) 的序列 \(A\)(初始全为 \(0\)),支持两种操作:单点 \(+1\)、全局大于 \(0\) 的元素 \(-1\)。每次操作后输出全序列的异或和。直接模拟全局减 \(1\) 会达到 \(O(NQ)\),需要优化。核心观察是:操作 \(2\) 相当于对所有非零元素执行 \(-1\),可用哈希表维护非零元素集合,仅遍历非零元素进行更新。

  2. 算法选择

    • 哈希表维护非零集合:用 map 记录当前值 \(> 0\) 的元素下标,操作 \(2\) 时只遍历这些元素
    • 异或增量更新:利用 \(ans \oplus= old \oplus new\) 的性质,\(O(1)\) 维护异或和
  3. 关键步骤

    • 初始化\(ans = 0\)\(A[1..N] = 0\)mp 为空
    • 操作 \(1\)(单点 \(+1\)
      • \(ans \oplus= A[x]\)(移除旧值)
      • \(A[x]++\)
      • \(ans \oplus= A[x]\)(加入新值)
      • mp[x] = 1(标记非零)
    • 操作 \(2\)(全局 \(-1\)
      • 遍历 mp 中所有非零元素 \(x\)
        • \(ans \oplus= A[x]\)(移除旧值)
        • \(A[x]--\)
        • \(ans \oplus= A[x]\)(加入新值)
        • \(A[x] = 0\):记录待删除
      • 批量删除变为 \(0\) 的下标
    • 每次操作后输出 \(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(Q \times K)\),其中 \(K\) 为操作 \(2\) 时的非零元素个数,均摊接近 \(O(Q)\)(每个元素最多被加减有限次)
    • 空间复杂度:\(O(N)\),存储序列和哈希表
  5. 哈希表优化的核心思想

    • 惰性全局操作:不真正对所有 \(N\) 个元素执行 \(-1\),而是维护"哪些元素需要被减",将 \(O(N)\) 降为 \(O(K)\)
    • 异或的增量性质\(ans = \bigoplus_{i=1}^{N} A_i\),修改 \(A_x\) 时只需 \(ans \oplus= old \oplus new\),无需重新计算全序列异或
    • 集合动态维护:操作 \(2\) 后元素可能变为 \(0\),需从集合中移除;操作 \(1\) 产生新的非零元素,需加入集合
    • 适用于带全局批量操作、需要维护聚合值的序列问题

【算法标签】

普及 #哈希表

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 500005; // 最大序列长度
int n, q, ans; // n:序列长度, q:查询数量, ans:当前所有元素的异或和
int a[N]; // a[i]:第i个元素的当前值
map<int, int> mp; // mp:记录当前值大于0的元素下标集合(值为1表示该位置非零)

int main()
{
    cin >> n >> q; // 读入序列长度和查询数量
    while (q--) // 循环处理每个查询
    {
        int op, x; // op:操作类型, x:操作参数
        cin >> op; // 读入操作类型
        if (op==1) // 操作1:将A_x增加1
        {
            cin >> x; // 读入要增加的位置x
            ans ^= a[x]; // 先将a[x]的旧值从异或和中移除
            a[x]++; // a[x]的值增加1
            ans ^= a[x]; // 将a[x]的新值加入异或和
            mp[x] = 1; // 标记该位置值大于0(用于操作2时遍历)
            cout << ans << endl; // 输出当前异或和
        }
        else // 操作2:将所有大于0的元素减1
        {
            vector<int> v; // 临时存储操作后变为0的元素下标
            for (auto t : mp) // 遍历所有当前值大于0的元素
            {
                ans ^= a[t.first]; // 先将该元素的旧值从异或和中移除
                a[t.first]--; // 该元素的值减1
                ans ^= a[t.first]; // 将该元素的新值加入异或和
                if (a[t.first]==0) // 如果减1后变为0
                    v.push_back(t.first); // 记录该下标,后续从map中删除
            }
            for (int i=0; i<v.size(); i++) // 删除所有变为0的元素下标
                mp.erase(v[i]);
            cout << ans << endl; // 输出当前异或和
        }
    }
    return 0;
}

【运行结果】

2 5
1 2
1
1 2
2
1 1
3
2
1
2
0
posted @ 2026-08-23 21:53  团爸讲算法  阅读(5)  评论(0)    收藏  举报