异或区间的划分
给定一个长度为 n
的整数数组 nums[1..n]
,求有多少种划分方式可以将它划分为若干个连续区间,使得每个区间的异或值都相同。
异或的一些性质
对于二进制
0 ^ 0 = 0
1 ^ 0 = 1
0 ^ 1 = 1
1 ^ 1 = 0
相同为0,不同为1
满足交换律、结合律:
-
a ⊕ b ⊕ c = a ⊕ c ⊕ b
-
a ⊕ a = 0
-
a ⊕ 0 = a
交换律: a ^ b = b ^ a
结合律:(a ^ b) ^ c = a ^ (b ^ c)
自反性 / 恒等律:a ^ 0 = a
自异性 / 自消律:a ^ a = 0
for (int i = 1; i <= n; i++)
{b[i] = b[i - 1] ^ nums[i];
}
-
nums[i]
:存储第i
个输入的数字,数组从下标 1 开始。 -
b[i]
:前缀异或数组。 -
b[i] = nums[1] ^nums[2] ... ^ nums[i]
根据自消率,当i>j时 ,b[i] ^ b[j]表示为 nums[j+1] ^ nums[j+2] ^ ... ^ nums[i]
b[j]表示为 nums[1] ^ nums[2] ^ ... ^ nums[j]
b[i]表示为 nums[1] ^ nums[2] ^ ... ^ nums[j]^ ...^ nums[i-1] ^nums[i]
枚举所有可能的目标值target
unordered_set<int> possible_targets;
for (int i = 1; i <= n; i++) {possible_targets.insert(b[i]);
}
异或区间的候选值
只考虑了前缀异或值作为 target 值候选,因为任何合法划分中,一段可能的异或值 target,至少得在某次 b[i] ^ b[j]
中出现过,而当 j == 0
,它就是 b[i]
。
再且不计,一段合理的划分,第一段区间异或值就是b[i]
使用 unordered_set 去重,每种可能的区间异或值只遍历一次。
对每个 target 值做一次动态规划计算方案数
long long ans = 0;
for (int target : possible_targets) {vector<long long> dp_target(n + 1, 0);dp_target[0] = 1;
dp_target[i]
:表示前 i
个数字中,以 i
为结尾、满足当前 target 的划分方案数
初始 dp_target[0] = 1
,表示空序列有一种合法划分(啥都不划)。
在我理解应该是在序列开头划一刀
/ 1 2 1 2 1
for (int i = 1; i <= n; i++) {for (int j = 0; j < i; j++) {if (i == n && j == 0) continue; // 防止整体作为一段被重复计算//有意忽略“整段都不划分”的情况,防止它重复计数,但如果想把“整体一段”算进去,应该删掉它。if ((b[i] ^ b[j]) == target) {dp_target[i] = (dp_target[i] + dp_target[j]) % MOD;}}
}
ans = (ans + dp_target[n]) % MOD;
-
判断是否可以将
[j+1..i]
作为一段,并且这段的异或值为target
。 -
如果是,那就可以从
dp_target[j]
转移到dp_target[i]
。 -
最终
dp_target[n]
表示从1
到n
形成合法划分的方案数。
#include <bits/stdc++.h>
using namespace std;
const int MOD = 998244353;int main() {int n;cin >> n;vector<int> nums(n + 1, 0);// b[i] = nums[1] ^ nums[2] ^ ... ^ nums[i]vector<int> b(n + 1, 0); // b[i] 表示 nums[1..i] 的前缀异或vector<long long> dp(n + 1, 0); // dp[i] 表示以 i 结尾的合法划分方案数// 输入序列for (int i = 1; i <= n; i++) {cin >> nums[i];}// 计算前缀异或for (int i = 1; i <= n; i++) {b[i] = b[i - 1] ^ nums[i];}// 初始化dp[0] = 1; // 空序列有一种划分方案// 使用 unordered_set 去重 ,存储所有可能的目标 区间 异或值unordered_set<int> possible_targets;for (int i = 1; i <= n; i++) {possible_targets.insert(b[i]); // 将每个前缀异或值添加到集合中}// 对每个可能的 异或值目标 进行 DPlong long ans = 0;for (int target : possible_targets) {vector<long long> dp_target(n + 1, 0);dp_target[0] = 1;for (int i = 1; i <= n; i++) {for (int j = 0; j < i; j++) {if(i==n&&j==0)continue;//跳过了只考虑整个数组作为一段的情况 // b[i] ^ b[j] 即为 nums[j+1..i] 的异或值if ((b[i] ^ b[j]) == target) // [j+1 : i] b[i] ^ b[j] 即为 nums[j+1..i] 的异或值{dp_target[i] = (dp_target[i] + dp_target[j]) % MOD;}}}ans = (ans + dp_target[n]) % MOD;}cout << ans << endl;//cout << ans + 1 << endl;//考虑整个数组作为一段,需要加1return 0;
}
示例;输入 5
序列 为 1 2 1 2 1
异或前缀和 b[ ] = [0, 1, 3, 2, 0, 1]
target = 0,1,2,3
模拟区间异或 target = 1
i
从 1
到 5
遍历,逐步更新 dp_target[i]
dp_target[0] = 1
/ 1 2 1 2 1
[
1 2 ] 1 2 1 表示为 取 [ x x x ]此区间的异或运算
j < i
i = 1
:
-
取target 为 b[1] = 1
-
对于所有
j
使得b[i] ^ b[j] == 1
: [ j+1 : i ]-
j = 0
-
b[1] ^ b[0] = 1 ^ 0 = 1
✅
[ 1 ] 2 1 2 1 -
满足条件的
j
,因此dp_target[1] += dp_target[0] = 1
-
/ 1 / 2 1 2 1
-
dp_target[1] = 1
-
i = 2
:
-
b[2] = 3
-
对于所有
j
使得b[i] ^ b[j] == 1
:-
j = 0
-
b[2] ^ b[0] = 3 ^ 0 = 3 ≠ 1 [
1 2 ] 1 2 1
不符合条件 -
j = 1
-
b[2] ^ b[1] = 3 ^ 1 = 2 ≠ 1
没有更多满足条件的j
,因此dp_target[2] = 0
-
i = 3
:
-
b[3] = 2
-
对于所有
j
使得b[i] ^ b[j] == 1
:-
j = 0
[
1 2 1 ] 2 1 -
b[3] ^ b[0] = 2 ^ 0 = 2 ≠ 1
-
j = 1 1 [ 2 1 ]2 1
-
b[3] ^ b[1] = 2 ^ 1 = 3 ≠ 1
-
j = 2 1 2 [ 1 ] 2 1
-
b[3] ^ b[2] = 2 ^ 3 = 1 ✅
-
这时,
dp_target[3] += dp_target[2] = 0
-
没有更多满足条件的
j
,因此dp_target[3] = 0
-
i = 4
:
-
b[4] = 0
-
对于所有
j
使得b[i] ^ b[j] == 1
:-
j = 0
[
1 2 1 2 ] 1 -
b[4] ^ b[0] = 0 ^ 0 = 0 ≠ 1
-
j = 1 1
[
2 1 2 ] 1 -
b[4] ^ b[1] = 0 ^ 1 = 1 ✅
-
dp_target[4] += dp_target[1] = 1
-
/ 1 / 2 1 2 / 1
-
j = 2 1 2
[
1 2 ] 1 -
b[4] ^ b[2] = 0 ^ 3 = 3 ≠ 1
-
j = 3
-
b[4] ^ b[3] = 0 ^ 2 = 2 ≠ 3
因此dp_target[4] = 1
-
/ 1 / 2 1 2 / 1
-
i = 5
:
-
b[5] = 1
-
对于所有
j
使得b[i] ^ b[j] == 1
:-
j = 0
-
b[5] ^ b[0] = 1 ^ 0 = 1 ✅
-
如果考虑整段划分,也就是不划分
-
dp_target[5] += dp_target[0] = 1
-
/ 1 2 1 2 1 /
-
程序中跳过了这种情况
-
j = 1
-
b[5] ^ b[1] = 1 ^ 1 = 0 ≠ 1
-
j = 2
-
b[5] ^ b[2] = 1 ^ 3 = 2 ≠ 1
-
j = 3
-
b[5] ^ b[3] = 1 ^ 2 = 3 ≠ 1
-
j = 4
-
b[5] ^ b[4] = 1 ^ 0 = 1 ✅
-
这时,
dp_target[5] += dp_target[4] = 1
-
/ 1 / 2 1 2 / 1 /
-
因此
dp_target[5] = 1
-
当区段异或值 区间目标值 target = 1 ,存在一种划分方法 :
/ 1 / 2 1 2 / 1 /
如果 考虑整段 ++ dp_target[5] = 2 就会有两种划分情况
/ 1 / 2 1 2 / 1 /
/ 1 2 1 2 1 /
示例2:
-
b[ ] = [0, 1, 3, 2, 0, 1]
-
target = 3
i = 1
:
-
b[1] = 1
-
对于所有
j
使得b[i] ^ b[j] == 3
:-
b[1] ^ b[0] = 1 ^ 0 = 1 ≠ 3 [
1] 2 1 2 1
没有满足条件的j
,因此dp_target[1] = 0
-
/ 1 2 1 2 1
-
2. i = 2
:
-
b[2] = 3
-
对于所有
j
使得b[i] ^ b[j] == 3
:-
b[2] ^ b[0] = 3 ^ 0 = 3 ✅ [
1 2] 1 2 1
这时,dp_target[2] += dp_target[0] = 1
-
/ 1 2 / 1 2 1
-
b[2] ^ b[1] = 3 ^ 1 = 2 ≠ 3
1 [ 2 ] 1 2 1
没有更多满足条件的j
,因此dp_target[2] = 1
-
3. i = 3
:
-
b[3] = 2
-
对于所有
j
使得b[i] ^ b[j] == 3
:-
b[3] ^ b[0] = 2 ^ 0 = 2 ≠ 3 [
1 2 1 ] 2 1 -
b[3] ^ b[1] = 2 ^ 1 = 3 ✅ 1 [2 1] 2 1
这时,dp_target[3] += dp_target[1] = 0
-
b[3] ^ b[2] = 2 ^ 3 = 1 ≠ 3
1 2 [1] 2 1
没有更多满足条件的j
,因此dp_target[3] = 0
-
4. i = 4
:
-
b[4] = 0
-
对于所有
j
使得b[i] ^ b[j] == 3
:-
b[4] ^ b[0] = 0 ^ 0 = 0 ≠ 3 [
1 2 1 2] 1 -
b[4] ^ b[1] = 0 ^ 1 = 1 ≠ 3
1[
2 1 2] 1 -
b[4] ^ b[2] = 0 ^ 3 = 3 ✅
1 2[
1 2] 1
这时,dp_target[4] += dp_target[2] = 1
-
/
1 2 / 1 2 / 1 -
b[4] ^ b[3] = 0 ^ 2 = 2 ≠ 3
1 2 1[
2 ] 1
因此dp_target[4] = 1
-
5. i = 5
:
-
b[5] = 1
-
对于所有
j
使得b[i] ^ b[j] == 3
:-
b[5] ^ b[0] = 1 ^ 0 = 1 ≠ 3 [
1 2 1 2 1 ] -
/ 1 2 1 2 1 /
-
整段划分 程序出跳过
-
b[5] ^ b[1] = 1 ^ 1 = 0 ≠ 3
1[
2 1 2 1 ] -
b[5] ^ b[2] = 1 ^ 3 = 2 ≠ 3
1 2[
1 2 1 ] -
b[5] ^ b[3] = 1 ^ 2 = 3 ✅
1 2 1[
2 1 ]
这时,dp_target[5] += dp_target[3] = 0
-
b[5] ^ b[4] = 1 ^ 0 = 1 ≠ 3
1 2 1 2[
1 ]
因此dp_target[5] = 0
-
在 target = 3
的情况下,最终没有有效的划分方案。
18413