Skip to content

Latest commit

 

History

History

1787.Make-the-XOR-of-All-Segments-Equal-to-Zero

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

1787.Make-the-XOR-of-All-Segments-Equal-to-Zero

首先,我们从A[0]^A[1]^A[2]...^A[k-1] = A[1]^A[2]...^A[k-1]^A[k]可以得到A[0]=A[k],以此类推,任何元素A[i]=A[i-k],也就是说我们要使得整个数组以k为周期循环。我们令Set{i}表示{A[i],A[i+k],A[i+2k]...}这一组需要改为相同值的元素。总共有k组。

那么对于每一组而言,我们改成什么数值最好呢?贪心的想法是改成该组里出现频次最多的那个元素,这样改动次数就可以最少。但显然,这不一定能达到全局最小的改动次数。事实上,甚至不一定要改成该组里任何曾经出现过的元素,也就是说,全局最优解有可能对应着该组的元素全部都要改动。

想到这里的话,似乎我们只能遍历所有的取值可能;也就是说,对于任何一组元素的改动方案,我们都要把[0,1023]都尝试一遍(题目中给定了每个元素的范围)。另外,因为每个元素的取值都在[0,1023],那么他们的XOR SUM也一定是在这个范围内,所以我们就可以设计DP算法。令dp[i][d]表示使得前i个元素的XOR-SUM等于d的最小改动(注意每次改动要把同组的元素都变一样)。那么突破口就是第i个元素的取值v,我们遍历它的可能:

for (int i=0; i<k; i++)
  for (int d=0; d<1024; d++)
    for (int v=0; v<1024; v++)
    {
      dp[i][d] = min(dp[i][d], dp[i-1][d^v] + cost{将第i组元素都改成v});
    }

对于上面的表达式有几点说明:

  1. d^v的本意是找一个元素x,使得前i-1个元素的XOR-SUM等于x,并且x^v之后的结果等于d。根据XOR的性质,我们可以知道这样的x其实就是d^v
  2. cost{将第i组元素都改成v}可以提前计算并存储起来。它的本质就是第i组元素的总个数,减去第i组元素里数值为v的总个数。前者记为totalCnt[i],后者记为count[i][v],后者的空间大小是kx1024,可以接受.
  3. 整体的时间复杂度是o(k*1024*1024),注定会TLE。

那么如何改进呢?我们将v的选择分为两部分,第一部分是第i组里面的元素,第二部分是第i组里没有出现的元素。如果只考虑前者,那么之前的代码就是:

for (int i=0; i<k; i++)
  for (int d=0; d<1024; d++)
    for (int j=i; j<n; j+=k)
    {
      int v = nums[j];
      dp[i][d] = min(dp[i][d], dp[i-1][d^v] + cost{将第i组元素都改成v});
    }

这是o(k*n/k*1024)的复杂度.

那么如果v是Set{i}里没有出现过的元素,我们也需要遍历v才能得到最小的dp[i][d]吗?事实上,我们可以预知哪个v能够得到最小的dp[i][d]。这是因为当v不在Set{i}里出现时,转移方程是

dp[i][d] = dp[i-1][d^v] + totalCnt[i]

其中的cost项是一个与v无关的常数。所以我们只需要找到最小的dp[i-1][d^v],那么得到的dp[i][d]必然也是最小的。那么“最小的dp[i-1][d^v]”是什么呢?只要将dp[i-1][x]里的x从[0,1023]扫一遍就行了。找到了最小dp[i-1][x]对应的x,那么就可以计算v=d^x。

但是这里有一个很迷惑的操作:你这样计算出来的v,不一定真的不在Set{i}里面。也就是说cost不见得真的就是totalCnt[i]。你按照v不在set{i}的方法去计算得到的最优方案v,有可能确实在Set{i}里面。但是这没有关系:因为这种情况下的cost=totalCnt[i]-count[i][v],比之前预期的totalCnt[i]更小,这一定是比v是非Set{i}元素更优的解。

综上所述,完整的算法描述为:

for (int i=0; i<k; i++)
  for (int d=0; d<1024; d++)
  {
    Find x, s.t. dp[i-1][x]最小
    v = d^x;
    dp[i][d] = dp[i-1][x] + cost{将第i组元素都改成v});
    
    for (int j=i; j<n; j+=k)
    {
      int v = nums[j];
      dp[i][d] = min(dp[i][d], dp[i-1][d^v] + cost{将第i组元素都改成v});
    }
  }  

注意,Find x, s.t. dp[i-1][x]最小这一行虽然要遍历1024个值,但与d无关,所以可以拿到d循环的外面。于是总的时间复杂度是o(k*( 1024 + 1024 * n/k).