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];
}
};