普通视图

发现新文章,点击刷新页面。
昨天 — 2025年1月18日首页

枚举 & DP

作者 tsreaper
2024年9月15日 00:16

解法:枚举 & 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;
    }
};
❌
❌