题解:洛谷 AT_arc098_b Xor Sum 2
【题目来源】
洛谷:AT_arc098_b [ABC098D] Xor Sum 2 - 洛谷
【题目描述】
给你一串数 \(a\)
求出满足\(a_l+\cdots +a_r=a_l\operatorname{xor}\cdots\operatorname{xor}a_r,l\le r\) 的 \((i,j)\) 的数量
\(1\le n\le 200000,\forall 1\le i\le n,0\le a_i<2^{20}(1048576)\)
【输入样例】
4
2 5 4 6
【输出样例】
5
【核心思想】
-
问题分析:给定一个长度为 \(n\) 的数组 \(a\),统计所有满足 \(\sum_{i=l}^{r} a_i = \bigoplus_{i=l}^{r} a_i\) 的连续子区间 \([l, r]\) 的个数。其中 \(\oplus\) 表示按位异或。由于加法存在进位,等式成立当且仅当区间内所有数的二进制表示中不存在重叠的 1 位,即区间内任意两个数的按位与为 0。等价于区间内所有数的按位或等于它们的按位异或(因为无进位)。利用这个性质,可以用双指针维护一个“合法”区间,统计所有子区间。
-
算法选择:
- 双指针 + 位计数:维护窗口 \([i, j]\),用
cnt[20]记录每个二进制位上 1 出现的次数,cur_or记录当前窗口所有数的按位或。扩展右指针时,检查新数 \(a[j+1]\) 与当前cur_or是否有公共位(cur_or & a[j+1] == 0),若满足则加入窗口,并更新cnt和cur_or;否则无法扩展,此时以 \(i\) 为左端点的所有合法子区间个数为 \(j - i + 1\)(因为窗口内任意后缀都合法)。然后移动左指针,从窗口中移除 \(a[i]\),更新计数,继续寻找。 - 复杂度:每个元素入队出队一次,每次更新 \(O(20)\),总时间复杂度 \(O(20n)\),空间 \(O(20)\)。
- 双指针 + 位计数:维护窗口 \([i, j]\),用
-
关键步骤:
- 读入 \(n\) 和数组 \(a[1..n]\)。
- 初始化左指针 \(i=1\),右指针 \(j=0\),
cur_or = 0,计数数组cnt[20] = {0},答案ans = 0(用long long)。 - 循环 \(i\) 从 1 到 \(n\):
- 尝试扩展右指针:当 \(j+1 \le n\) 且
(cur_or & a[j+1]) == 0时,将 \(a[j+1]\) 加入窗口:更新cnt中各二进制位计数,若某位计数从 0 变为 1 则cur_or |= (1<<bit);j++。 - 此时以 \(i\) 为左端点的合法区间数为 \(j - i + 1\),累加到
ans。 - 准备移动左指针:从窗口中移除 \(a[i]\):遍历其二进制位,若某位为 1,则
cnt[bit]--,若减为 0 则cur_or &= ~(1<<bit);i++。
- 尝试扩展右指针:当 \(j+1 \le n\) 且
- 输出
ans。
-
时间/空间复杂度:
- 时间复杂度:\(O(n \cdot \log A)\),其中 \(A \le 2^{20}\),常数很小。
- 空间复杂度:\(O(1)\)(只使用了固定大小的计数数组)。
-
双指针 + 位运算的核心思想:
- 等式转化:利用 \(sum \ge xor\),相等条件等价于区间内所有数在二进制位上互不重叠,即任意两数按位与为 0。
- 窗口合法性:窗口内所有数的按位或
cur_or与待加入数无公共位时,加入后仍合法,保证了窗口内所有子区间均合法。 - 计数方式:固定左指针,向右扩展右指针直到不合法,则所有以当前左指针为起点的合法区间数就是当前窗口长度,体现了双指针的单调性。
- 高效维护:通过位计数数组支持 \(O(1)\) 级别的加入和移除操作,避免了重新计算按位或。
【算法标签】
普及 #双指针
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long // 使用长整型
const int MAX_N = 200005; // 定义数组最大长度
int n; // 数组长度
int a[MAX_N]; // 存储原始数组
int s1, s2; // s1: 累加和, s2: 异或和
int ans; // 存储最终结果(满足条件的子数组个数)
signed main()
{
// 输入数组长度
cin >> n;
// 输入数组元素
for (int i = 1; i <= n; i++)
cin >> a[i];
// 初始化双指针和累加器
int i = 1, j = 0; // i: 左指针, j: 右指针
s1 = 0, s2 = 0; // 初始化累加和与异或和
// 滑动窗口法统计满足条件的子数组
while (i <= n)
{
// 扩展右边界直到不满足条件
while (j + 1 <= n && s1 + a[j + 1] == (s2 ^ a[j + 1]))
{
j++; // 右指针右移
s1 += a[j]; // 更新累加和
s2 ^= a[j]; // 更新异或和
}
// 统计以i为左端点的满足条件的子数组个数
ans += j - i + 1;
// 移动左指针
s1 -= a[i]; // 从累加和中移除a[i]
s2 ^= a[i]; // 从异或和中移除a[i]
i++; // 左指针右移
}
// 输出结果
cout << ans << endl;
return 0;
}
// 使用acwing模板二刷
#include <bits/stdc++.h>
using namespace std;
#define int long long // 使用长整型
const int N = 200005; // 定义数组最大长度
int n; // 数组长度
int a[N]; // 存储原始数组
int s1, s2; // s1: 累加和, s2: 异或和
int ans; // 存储最终结果(满足条件的子数组个数)
signed main()
{
// 输入数组长度
cin >> n;
// 输入数组元素
for (int i = 1; i <= n; i++)
cin >> a[i];
// 初始化双指针和累加器
int i = 1, j = 0; // i: 左指针, j: 右指针
s1 = 0, s2 = 0; // 初始化累加和与异或和
// 滑动窗口法统计满足条件的子数组
for (; i <= n; i++)
{
// 扩展右边界直到不满足条件
while (j + 1 <= n && s1 + a[j + 1] == (s2 ^ a[j + 1]))
{
j++; // 右指针右移
s1 += a[j]; // 更新累加和
s2 ^= a[j]; // 更新异或和
}
// 统计以i为左端点的满足条件的子数组个数
ans += j - i + 1;
// 移动左指针
s1 -= a[i]; // 从累加和中移除a[i]
s2 ^= a[i]; // 从异或和中移除a[i]
}
// 输出结果
cout << ans << endl;
return 0;
}
【运行结果】
4
2 5 4 6
5
浙公网安备 33010602011771号