不能被 K 整除的两个和的最大子集

Posted

技术标签:

【中文标题】不能被 K 整除的两个和的最大子集【英文标题】:Maximum subset which has no sum of two divisible by K 【发布时间】:2012-12-09 17:05:41 【问题描述】:

给定集合 1, 2, 3, ... ,N。我必须找到给定集合的子集的最大大小,以便子集中的任何 2 个数字的总和不能被给定的数字 K 整除。N 和 K 可以达到 2*10^9,所以我需要一个非常快的算法。我只想出了一个复杂度为 O(K) 的算法,它很慢。

【问题讨论】:

输入集是否总是从 1 到 N 的连续数字? 是的,输入只包含数字 N 和 K,这意味着我在集合中有数字 1,2,3,4, ...,N。 子集基数或子集值总和的最大大小?你只需要大小还是实际的子集? 【参考方案1】:

首先计算所有的集合元素 mod k。然后解决简单的问题: 求给定集合的子集的最大大小,使得子集中任意 2 个数的和不等于给定数 K。 我将此集合分为两组(i和k-i),您不能同时选择set(i)和set(k-i)。

int myset[]
int modclass[k]

for(int i=0; i< size of myset ;i++)

    modclass[(myset[i] mod k)] ++;

选择

for(int i=0; i< k/2 ;i++)
 
    if (modclass[i] > modclass[k-i])
    
        choose all of the set elements that the element mod k equal i
    
    else
    
        choose all of the set elements that the element mod k equal k-i
    

最后,您可以从元素 mod k 等于 0 或 k/2 中添加一个元素。

这个解决方案的算法复杂度为 O(K)。

你可以用动态数组来改进这个想法:

for(int i=0; i< size of myset ;i++)

    x= myset[i] mod k;
    set=false;
    for(int j=0; j< size of newset ;j++)
    
        if(newset[j][1]==x or newset[j][2]==x)
        
            if (x < k/2)
            
                newset[j][1]++;
                set=true;
            
            else
            
                newset[j][2]++;
                set=true;
            
        
    
    if(set==false)
    
        if (x < k/2)
        
            newset.add(1,0);
        
        else
        
            newset.add(0,1);
        
    

现在您可以选择复杂度为 O(myset.count) 的算法。您的算法比 O(myset.count) 还多,因为您需要 O(myset.count) 来读取您的集合。 该解决方案的复杂度为 O(myset.count^2),您可以根据输入选择算法。比较 O(myset.count^2) 和 o(k)。 为了获得更好的解决方案,您可以根据 mod k 对 myset 进行排序。

【讨论】:

这看起来像是对任意自然数集的一般问题的解决方案。鉴于集合是数字 1 到 N 的信息,我相信应该有一个基于仅涉及 N 和 K 的计算的 O(1) 解决方案。【参考方案2】:

我假设对于某些 N,数字集始终是 1 到 N。

考虑前 N-(N mod K) 个数字。 K 个连续数的形式 floor(N/K) 序列,从 0 到 K-1 减少 mod K。对于每个组,必须删除 floor(K/2) 以获得归约 mod K,它是 floor(K/2) 的另一个子集的否定 mod K。您可以保留每组 K 个连续数字的上限(K/2)。

现在考虑剩余的 N mod K 数。他们从 1 开始减少 mod K。我还没有计算出确切的限制,但如果 N mod K 小于 K/2 左右,您将能够保留所有这些限制。如果没有,您将能够保留其中的第一个上限(K/2)。

================================================ =============================

我相信这里的概念是正确的,但我还没有弄清楚所有的细节。

================================================ =============================

这是我对问题的分析和答案。在接下来的 |x|是地板(x)。此解决方案类似于@Constantine 的答案中的解决方案,但在某些情况下有所不同。

考虑第一个 K*|N/K|元素。它们由 |N/K| 组成以 K 为模重复减少。

一般来说,我们可以包含 |N/K| k 模 K 的元素受到以下限制:

如果 (k+k)%K 为零,我们只能包含一个 k 模 K 的元素。这就是 k=0 和 k=(K/2)%K 的情况,这只会发生在甚至K。

这意味着我们得到 |N/K| * |(K-1)/2|重复的元素。

我们需要纠正省略的元素。如果 N >= K 我们需要为 0 mod K 元素加 1。如果 K 是偶数且 N>=K/2,我们还需要为 (K/2)%K 个元素加 1。

最后,如果 M(N)!=0,我们需要添加重复元素的部分或完整副本 min(N%K,|(K-1)/2|)。

最终公式为:

|N/K| * |(K-1)/2| +
(N>=K ? 1 : 0) +
((N>=K/2 && (K%2)==0) ? 1 : 0) +
min(N%K,|(K-1)/2|)

这与@Constantine 的版本不同,在某些情况下甚至涉及 K。例如,考虑 N=4,K=6。正确答案是 3,即集合 1, 2, 3 的大小。 @Constantine 的公式给出 |(6-1)/2| = |5/2| = 2。上面的公式前两行各取 0,第三行取 1,最后一行取 2,给出正确答案。

【讨论】:

我无法完全考虑。但我猜对了你的解决方案,我投了赞成票,但你的解决方案无法将元素发送到输出:D @amink 感谢您的支持。问题是“找到子集的最大大小”,而不是“找到最大的子集”,所以我没有尝试生成子集,只计算它的大小。该问题还要求快速解决方案。我的解决方案是 O(1)。对于 K>1,任何生成集合的解都是 \Omega(N)。【参考方案3】:

公式是

|N/K| * |(K-1)/2| + ost 

ost =
if n<k:
  ost =0
else if n%k ==0 :
  ost =1    
else if n%k < |(K-1)/2| :
  ost = n%k
else:
  ost = |(K-1)/2|

在哪里 |a/b| 例如 |9/2| = 4 |7/2| = 3

示例 n = 30 , k =7 ;

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

1 2 3 |4| 5 6 7. - 是第一行。 8 9 10 |11| 12 13 14 - 二线 如果我们在每行中获得前 3 个数字,我们可能会获得该子集的大小。我们也可以从 (7 14 28) 中添加一个数字

得到前 3 个数字 (1 2 3) 是一个数字 |(k-1)/2| . 这一行的编号是|n/k| . 如果没有剩余,我们可以添加一个数字(例如最后一个数字)。 如果残差

感谢例外情况。 ost = 0 如果 k>n

【讨论】:

我认为公式是正确的。如果有一些解释,答案会更好。 经过进一步思考,在我看来,如果 K 是偶数,它无法解释包含 K/2 的一份副本。例如,它给出 N=4、K=6 的答案 2。正确答案是 3,即 1, 2, 3 的大小。请参阅我的答案以进行分析。【参考方案4】:
n,k=(raw_input().split(' '))
n=int(n)
k=int(k)
l=[0 for x in range(k)]
d=[int(x) for x in raw_input().split(' ')]
flag=0
for x in d:
   l[x%k]=l[x%k]+1

sum=0

if l[0]!=0:
    sum+=1 
if (k%2==0):
    sum+=1


if k==1:
    print 1
elif k==2:
    print 2 
else:       
    i=1
    j=k-1
    while i<j:
        sum=sum+(l[i] if l[i]>=l[j] else l[j])
        i=i+1
        j=j-1
     print sum

【讨论】:

你能补充一些解释为什么这很适合这个问题吗? 虽然此代码可能有助于解决问题,但提供有关 why 和/或 如何 回答问题的额外上下文将显着改善其长期长期价值。请edit你的答案添加一些解释。【参考方案5】:

这是对 ABRAR TYAGI 和 amin k 解决方案的解释。

此解决方案的方法是:

创建一个包含 K 个存储桶的数组 L,并将来自 将数组 D 输入到 K 个桶中。每个桶 L[i] 包含 D 的元素,使得 ( element % K ) = i。 所有可以单独被 K 整除的元素都在 L[0] 中。所以 这些元素中只有一个(如果有)可以属于我们的最终(最大) 子集。这些元素中任意两个的总和都可以被 K 整除。 如果我们将 L[i] 中的一个元素添加到 L[K-i] 中的一个元素,则总和可被 K 整除。因此,我们可以仅将其中一个桶中的元素添加到 我们的最后一组。我们选择最大的桶。

代码: d 是包含大小为 n 的初始数字集的数组。这段代码的目标是找到 d 的最大子集的计数,使得没有两个整数之和可以被 2 整除。

l 是一个包含 k 个整数的数组。想法是将数组 d 中的每个(元素)减少为(元素 % k),并将它们出现的频率保存在数组 l 中。

例如l[1]包含所有元素的频率% k = 1

我们知道 1 + (k-1) % k = 0 因此必须丢弃 l[1] 或 l[k-1] 以满足两个数字之和 % k 不应为 0 的标准。

但由于我们需要 d 的最大子集,我们选择 l[1] 和 l[k-1] 中较大的一个

我们遍历数组 l 使得 for (i=1; i

有两个异常值。 l[0] 组中任意两个数之和 % k = 0。所以如果 l[0] 不为零,则加 1。

如果 k 为偶数,则循环不处理 i=k/2,并使用与上述相同的逻辑将计数加一。

【讨论】:

这是对@ABRAR TYAGI 和 amin k 解决方案的解释。

以上是关于不能被 K 整除的两个和的最大子集的主要内容,如果未能解决你的问题,请参考以下文章

能被 K 整除的最大连续子串长度

1104.整除问题

368. 最大整除子集(序列dp)

整除问题(王道)

LeetCode 0368. 最大整除子集

最大整除子集