最小变化,因此每k个连续元素的XOR为0

问题描述

我认为此任务的在线法官已经过期。鉴于以下我提出的解决方案,从逻辑上讲合理吗?我们可以在时间或空间复杂度方面做得更好吗?实用的蛮力方法看起来像什么?

任务:

给出长度为n的数组,找到需要更改的最小元素数,以使每个k连续元素的XOR为0。

约束:

1 ≤ k ≤ n ≤ 10^4
0 ≤ A[i] < 1024

建议的解决方案:

说我们对前k个元素有一个最佳选择。为了将当前窗口更新为下一个连续的k元素,我们删除了第一个元素和与下一个提议的元素进行XOR的贡献。为了删除一个元素的贡献,我们对其进行XOR,这意味着将下一个窗口XOR设为零的唯一选择是与刚刚删除的元素进行XOR。这意味着最佳的前k个元素必须始终重复进行。

e1,e2,e3,...ek,e1,etc.

让我们调用必须彼此相等的元素A[i],A[i+k],A[i+2*k]...的每个序列seq(i)。我们可以通过注意如果只允许将 seq(i)的元素设置为任意一个元素,就可以计算出所需更改次数的最小上限。对其余seq(i)个可行的解决方案进行选择,包括每个选择的成本最低。

要尝试比最小上限做得更好,我们排除使用任何任意分配,因此每个seq(i)的所有目标选项都必须来自集合seq(i)本身。此外,在进行迭代时,我们可以使用最小上限来排除代价相同或更多的任何XOR前缀。

时间复杂度O(k * n/k * 1024) = O(n)。空间复杂度O(n)

Python 3示例:

from collections import defaultdict
from math import ceil

A = [1,2,3,1,4]
k = 3

n = len(A)

seqs = [None] * k

for i in range(k):
  seqs[i] = defaultdict(lambda: 0)

  for j in range(i,n,k):
    seqs[i][A[j]] += 1

def cost(i,e):
  return ceil((n - i) / k) - seqs[i][e]
  
def min_cost(i):
  return min([cost(i,e) for e in seqs[i]])
  
total_min_cost = sum([min_cost(i) for i in range(k)])

upper_bound = total_min_cost + min([ceil((n - i) / k) - min_cost(i) for i in range(k)])

dp = {0: 0}

for i in range(k):
  new_dp = defaultdict(lambda: float('inf'))

  for e in seqs[i]:
    for xor_pfx in dp:
      new_cost = cost(i,e) + dp[xor_pfx]

      if new_cost < upper_bound:
        new_pfx = xor_pfx ^ e
        new_dp[new_pfx] = min(new_dp[new_pfx],new_cost)

  dp = new_dp
  
result = dp[0] if 0 in dp else upper_bound

print(result)

解决方法

如OP所述,必须满足两个条件才能获得有效序列:

1. xor-sum_(i=0 to K-1) A[i] = 0
2. A[i+K] = A[i] for all i

这意味着构建这样的序列具有“ K-1”个自由度。
注意:这种序列可以解释为大小为K-1的信息序列的信道编码,并带有简单的奇偶校验编码(条件1.,获得长度为K的序列)的串联)重复编码(条件2->长度N)。然后,练习包括纠正传输通道引入的错误。通道结束后,不再考虑条件,最可靠的估计是重建正确的序列,同时引入尽可能少的修改(校正)。

让我们称S[i]对应于相同值的K集。 S[i] = {A[i],A[i+K],A[i+2*K],...},和i: 0 -> K-1
然后我们将L[i]的大小称为S[i]

第一步包括对重复码进行解码的尝试,即确定每个集合S[i]中哪个或哪些是最佳估计。从逻辑上讲,最佳估计在于为每个集合找到最代表的值。对于每个集合S[i]和每个可能的值jj从0到Amax,在这里Amax = 1023),j的可靠性为等于它在Set[i]中出现的次数。实际上:

Reliab[i][j]++ each times `j` appears in `S[i]`. 
and then,Cost[i][j] = L[i] - Reliab[i][j]

通过最大化每个集合的可靠性,我们得到集合B[i]的估计E[i]
此时,如果估算值符合奇偶校验条件:

xor-sum B[i] = 0

然后我们找到了我们的估计,更改的数量对应于下限:

lower_bound = sum(L[i] - reliab[i][B[i]])

但是,在一般情况下,不遵守奇偶校验条件,因此我们需要找到更改次数最少的方法。一种相当简单的可能性在于仅修改一个估计,该估计对应于最小附加成本。例如,如果我们接受修改估算B[i],则必须将其替换为

C[i] = xor-sum B[j],for j different of i. 

则更改的附加数量等于

add_cost[i] = Reliab[B[i]] - Reliab[C[i]]]

但是,这种修改一个以前的估算值的解决方案仅不能确保在所有时间上都将变更数量最小化。

要解决此问题,一种可能性(蛮力!)是迭代计算与所有可能性相对应的所有成本。

For (i: 0 -> K-1) For (j: 0 -> Amax)
    cumul_cost[i][j] = min(k) {cumul_cost[i-1][j^k] + Cost[i][k]} (k = 0 to Amax)

然后,答案等于cumul_cost[K-1][0]

问题在于此方法的复杂度等于O(N + K * Amax ^ 2),似乎太高了。

至少,此解决方案易于实现,应该为检查更简单的解决方案提供参考。

在此方法中,考虑了许多中间结果,这些结果不能对应于可行的解决方案。 一个应该更好的实用解决方案是实现回溯,同时优先考虑更可靠的元素。

这可以通过对集合E[i]进行排序来获得,并且在当前修改数量大于当前获得的最佳解决方案时,无需进一步探索DFS分支。