力扣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];
    }
};
posted @ 2021-05-25 21:15  blueattack  阅读(49)  评论(0编辑  收藏  举报