📅  最后修改于: 2023-12-03 15:12:46.106000             🧑  作者: Mango
本题为 "门|门 CS 1999" 系列中的第 51 题,题目难度为普及/提高-。
给定一个长度为 $n$ 的正整数序列 $a$ 和一个正整数 $k$,请你求出定长为 $k$ 的连续子序列,使得这 $k$ 个数的异或和最大。
第一行是两个正整数 $n$ 和 $k$,表示序列 $a$ 的长度和所求子序列的长度。
第二行包含 $n$ 个正整数,表示序列 $a$。
共一行,包含一个正整数,表示定长连续子序列的最大异或和。
5 2
5 2 7 8 6
15
$1\leqslant n\leqslant 10^5,1\leqslant k\leqslant 100,\sum\limits_{i=1}^nk\leqslant 10^5,0\leqslant a_i<2^{32}$
首先我们考虑朴素做法,用双重循环枚举所有的定长连续子序列,然后求出它们的异或和,取最大值即可。复杂度为 $O(nk^2)$,显然不能通过本题。
我们发现,对于一个连续子序列 $[l,r]$,它的异或和为:
$S_{l,r}=a_l\oplus a_{l+1}\oplus\cdots\oplus a_r$
如果我们已经求出了 $[l+1,r]$、$[l,r-1]$ 的异或和,可以得到:
$S_{l,r}=a_l\oplus S_{l+1,r}=S_{l,r-1}\oplus a_r$
基于异或的性质,我们可以 $O(1)$ 完成一个子区间的查询,这启示我们可以用 dp 求解本题。
假设 $f_{i,j}$ 为前 $i$ 个元素,长度为 $j$ 的连续子序列的最大异或和,那么转移方程为:
$f_{i,j}=\max{f_{i-1,j},f_{i-1,j-1}\oplus a_i}$
其中,$f_{i-1,j}$ 表示不选择当前元素 $a_i$,$f_{i-1,j-1}\oplus a_i$ 表示选择当前元素 $a_i$。
最终答案即为 $\max\limits_{i=k}^n f_{i,k}$。
时间复杂度 $O(nk)$。
#include <bits/stdc++.h>
using namespace std;
typedef unsigned int uint;
const int N = 1e5 + 10, K = 110;
int n, k;
uint a[N];
uint f[N][K];
uint calc(uint x) {
for (int i = 31; ~i; i--) {
if (x >> i & 1) {
return x ^ ((1 << i) - 1);
}
}
return 0;
}
int main() {
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
a[i] = calc(a[i]);
}
for (int i = 1; i <= n; i++) {
for (int j = k; j; j--) {
f[i][j] = max(f[i-1][j], f[i-1][j-1] ^ a[i]);
}
}
uint ans = 0;
for (int i = k; i <= n; i++) {
ans = max(ans, f[i][k]);
}
cout << ans << endl;
return 0;
}