Leetcode5700. 使所有区间的异或结果为零(DP)

题意

\(n\)个数和一个\(k\) 可以对一个数任意修改 要求修改最少的数 使得任意连续\(k\)个数 xor的和等于0
\(a[i] <= 2^{10}\)

题解

显然可以发现一个规律 假如\(k=3\)修改后的序列都形如\(abcabcabc\).... 相同字母表示相同的值
那么可以按\(k\)分组 \(a[i]\),\(a[i+k]\),\(a[i+2k]\)...的分为同一组 同一组的值最后是一样的
\(dp[val]\)表示已经枚举完了一些组 值为\(val\)的最小操作次数
转移有两种

1)全部修改为一个这组没出现的数

因为修改成的这个数可以是任意的 所以任意\(val\)的值都能由前面的最小值来转移过来

2)全部修改为一个这组出线过的一个数

依次枚举前面的\(val\)和选这一组出现过的一个数 单看这里以为总共的复杂度就是 \(O(k*k*2^{10})\) 就觉得不可做了
其实就是一个经典错误.. 和树上背包一样 其实复杂度是\(O(k* \frac{n}{k} *2^{10})\)

class Solution {
public:
    const int INF = 0x3f3f3f3f;
    int minChanges(vector<int>& nums, int k) {
        int n = nums.size();
        vector<int> dp(1 << 10, INF);
        dp[0] = 0;
        vector<unordered_map<int, int>> g(k);
        vector<int> sz(k);
        for(int i = 0; i < k; i++) {
            for(int j = i; j < n; j += k) {
                g[i][nums[j]]++;
                sz[i]++;
            }
        }
        for(int i = 0; i < k; i++) {
            int zx = *min_element(dp.begin(), dp.end());
            vector<int> tmp(1 << 10, zx + sz[i]);
            for(int j = 0; j < (1 << 10); j++) {
                if(dp[j] == INF) continue;
                for(auto [a, b] : g[i]) {
                    int nx = a ^ j;
                    tmp[nx] = min(tmp[nx], dp[j] + sz[i] - b);
                }
            }
            dp = move(tmp);
        }
        return dp[0];
    }
};
posted @ 2021-03-07 20:00  lwqq3  阅读(192)  评论(0编辑  收藏  举报