枚举 & DP
解法:枚举 & DP
一般这种一个序列分两半的题目,我们会尝试枚举分界点。
设 $f(i, j, x)$ 表示从前 $i$ 个元素中选出 $j$ 个,OR 起来能否得到 $x$;$g(i, j, x)$ 表示从第 $i$ 个元素到最后一个元素中选出 $j$ 个,OR 起来能否得到 $x$。我们枚举子序列左半边的结束点 $i$,左半边 OR 起来的值 $x$,以及右半边 OR 起来的值 $y$,那么答案就是
$$
\max\limits_{f(i, k, x) = \text{true and } g(i + 1, k, y) = \text{true}} x \oplus y
$$
枚举的复杂度是 $\mathcal{O}(n \times m^2)$,其中 $m = 2^7$ 是题目中提到的取值范围。
剩下的问题就是 $f$ 和 $g$ 怎么求。考虑是否选择第 $i$ 个元素,可以得到转移方程
f(i, j, x) -> f(i + 1, j, x) // 不选 nums[i]
f(i, j, x) -> f(i + 1, j + 1, x | nums[i]) // 选 nums[i]
初值 f(0, 0, 0) = true
,其它位置初值为 false
。$g$ 的求法类似。这一部分的复杂度为 $\mathcal{O}(nkm)$。
参考代码(c++)
###cpp
bool f[410][210][1 << 7], g[410][210][1 << 7];
class Solution {
public:
int maxValue(vector<int>& nums, int K) {
int n = nums.size();
int m = 1 << 7;
for (int i = 0; i <= n + 1; i++) for (int j = 0; j <= K; j++) for (int x = 0; x < m; x++)
f[i][j][x] = g[i][j][x] = false;
auto update = [&](bool &x, bool y) { x = x || y; };
// DP 求子序列前半部分的情况
f[0][0][0] = true;
for (int i = 0; i < n; i++) for (int j = 0; j <= K; j++) for (int x = 0; x < m; x++) if (f[i][j][x]) {
update(f[i + 1][j][x], f[i][j][x]);
if (j < K) update(f[i + 1][j + 1][x | nums[i]], f[i][j][x]);
}
// DP 求子序列后半部分的情况
g[n + 1][0][0] = true;
for (int i = n + 1; i > 1; i--) for (int j = 0; j <= K; j++) for (int x = 0; x < m; x++) if (g[i][j][x]) {
update(g[i - 1][j][x], g[i][j][x]);
if (j < K) update(g[i - 1][j + 1][x | nums[i - 2]], g[i][j][x]);
}
int ans = 0;
// 枚举子序列的分界点,以及前后半的值
for (int i = K; i + K <= n; i++) for (int x = 0; x < m; x++) for (int y = 0; y < m; y++)
if (f[i][K][x] && g[i + 1][K][y]) ans = max(ans, x ^ y);
return ans;
}
};