力扣1787
题目
给你一个整数数组 nums 和一个整数 k 。区间 [left, right](left <= right)的 异或结果 是对下标位于 left 和 right(包括 left 和 right )之间所有元素进行 XOR 运算的结果:nums[left] XOR nums[left+1] XOR ... XOR nums[right] 。
返回数组中 要更改的最小元素数 ,以使所有长度为 k 的区间异或结果等于零。
题解
A1^A2^...^Ak=0
A2^A3^....^Ak+1=0
..
根据上式可得
A1=Ak+1
A2=Ak+2
A3=Ak+3
...
只关注前k个数字异或和为0
因此题目变成求解前k个数字异或和为0,其余数字变成前k个数字后尽可能修改的小
[1,2,3, 1,2,5, 1,2,6]
a=1: 代价为3-3=0 (all-cur) cur是要改成某个数字,在当前index下有多次出现
a=2: 代价为3-0=3
前k个数字等于什么的代价不同,代价需要预先计算
dp[i][v]表示前i个数字异或和为v的最小代价是多少
dp[i-1][v] +cost(s) --> dp[i][v^s]
如果单纯暴力枚举v和s,总的时间复杂度为O(102410242000)
而实际计算过程中,每个index%k的位置上实际上由很多重复的数字,对于未出现的数字,可以一次计算
const int MAXN=2010;
const int MAXV=1024;
int all[MAXN];
unordered_map<int,int>cur[MAXN];
int dp[MAXN][MAXV];
//O(K*V+N*V) 只关注前k个,dp[i][s]表示前i个词,异或和为s时的最小交换次数
class Solution {
public:
int minChanges(vector<int>& nums, int k) {
int n=nums.size();
for(int i=0;i<n;i++) cur[i].clear();
memset(all,0,sizeof(all));
for(int i=0;i<n;i++){
all[i%k]++; //第i%k的个数
cur[i%k][nums[i]]++; //第i%k个下标数字nums[i]出现的次数
}
memset(dp,-1,sizeof(dp));
dp[0][0]=0;
for(int i=1;i<=k;i++){
int mi=2010;
for(int s=0;s<MAXV;s++){ //预先处理出一个上界
if(dp[i-1][s]==-1) continue;
if(mi>dp[i-1][s]) mi=dp[i-1][s];
}
mi+=all[i-1]; //未出现在第i个位置的数字(上界)
for(int s=0;s<MAXV;s++){
if(dp[i][s]==-1||dp[i][s]>mi) dp[i][s]=mi;
if(dp[i-1][s]==-1) continue;
for(auto it=cur[i-1].begin();it!=cur[i-1].end();it++){ //在位置i出现的所有数字
int v=it->first,c=it->second;
int cost=dp[i-1][s]+all[i-1]-c;
if(dp[i][s^v]==-1||dp[i][s^v]>cost) dp[i][s^v]=cost;
}
}
}
return dp[k][0];
}
};