所以,这个问题我没有任何线索如何解决它的问题陈述是:
给定N个整数的集合S,任务决定是否可能 将它们分成K个非空子集,使得它们中的元素之和 每个K子集都是相等的。
N可以是最大值20.K可以是最大值8
问题是要使用DP + Bitmasks专门解决!
我无法理解从哪里开始!由于有K套维护,我不能把K各自代表一些或另一个!
如果我尝试将整个集合作为状态而将K作为另一个,我在创建循环关系时遇到问题!
你能帮忙吗?原始问题Problem
的链接答案 0 :(得分:4)
你可以在O(N * 2 ^ N)中解决问题,因此K对于复杂性没有意义。
首先让我警告你角落情况N< K所有数字都为零,答案为“否”。
我的算法的想法如下。假设我们已经计算了每个掩模的总和(可以在O(2 ^ N)中完成)。我们知道,对于每个组,总和应该是总和除以K.
我们可以使用掩码执行DP,其中状态只是一个二进制掩码,告诉我们使用了哪些数字。从算法复杂度中删除K的关键思想是注意到如果我们知道使用了哪些数字,我们知道到目前为止的总和,所以我们也知道我们现在正在填充哪个组(当前和/组和)。然后尝试选择组的下一个数字:如果我们不超过组预期总和,它将有效。
您可以查看我的C ++代码:
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
typedef long long ll;
ll v[21 + 5];
ll sum[(1 << 21) + 5];
ll group_sum;
int n, k;
void compute_sums(int position, ll current_sum, int mask)
{
if (position == -1)
{
sum[mask] = current_sum;
return;
}
compute_sums(position - 1, current_sum, mask << 1);
compute_sums(position - 1, current_sum + v[position], (mask << 1) + 1);
}
void solve_case()
{
cin >> n >> k;
for (int i = 0; i < n; ++i)
cin >> v[i];
memset(sum, 0, sizeof(sum));
compute_sums(n - 1, 0, 0);
group_sum = sum[(1 << n) - 1];
if (group_sum % k != 0)
{
cout << "no" << endl;
return;
}
if (group_sum == 0)
{
if (n >= k)
cout << "yes" << endl;
else
cout << "no" << endl;
return;
}
group_sum /= k;
vector<int> M(1 << n, 0);
M[0] = 1;
for (int mask = 0; mask < (1 << n); ++mask)
{
if (M[mask])
{
int current_group = sum[mask] / group_sum;
for (int i = 0; i < n; ++i)
{
if ((mask >> i) & 1)
continue;
if (sum[mask | (1 << i)] <= group_sum * (current_group + 1))
M[mask | (1 << i)] = 1;
}
}
}
if (M[(1 << n) - 1])
cout << "yes" << endl;
else
cout << "no" << endl;
}
int main()
{
int cases;
cin >> cases;
for (int z = 1; z <= cases; ++z)
solve_case();
}
答案 1 :(得分:3)
这是JavaScript中的工作O(K * 2 ^ N * N)实现。来自伪代码https://discuss.codechef.com/questions/58420/sanskar-editorial
function equality(set, size, count) {
if(size < count) { return false; }
var total = set.reduce(function(p, c) { return p + c; }, 0);
if((total % count) !== 0) { return false }
var subsetTotal = total / count;
var search = {0: true};
var nextSearch = {};
for(var i=0; i<count; i++) {
for(var bits=0; bits < (1 << size); bits++){
if(search[bits] !== true) { continue; }
var sum = 0;
for(var j=0; j < size; j++) {
if((bits & (1 << j)) !== 0) { sum += set[j]; }
}
sum -= i * subsetTotal;
for(var j=0; j < size; j++) {
if((bits & (1 << j)) !== 0) { continue; }
var testBits = bits | (1 << j);
var tmpTotal = sum + set[j];
if(tmpTotal == subsetTotal) { nextSearch[testBits] = true; }
else if(tmpTotal < subsetTotal) { search[testBits] = true; }
}
}
search = nextSearch;
nextSearch = {};
}
if(search[(1 << size) - 1] === true) {
return true;
}
return false;
}
console.log(true, equality([1,2,3,1,2,3], 6, 2));
console.log(true, equality([1, 2, 4, 5, 6], 5, 3));
console.log(true, equality([10,20,10,20,10,20,10,20,10,20], 10, 5));
console.log(false, equality([1,2,4,5,7], 5, 3));
编辑该算法找到符合条件的所有位掩码(代表子集位)(总和 tmpTotal 小于或等于理想子集sum subsetTotal )。通过所需的子集数量 count 重复此过程,您可以使用位掩码,其中设置所有 size 位,这意味着成功或测试失败。
示例强>
设置 = [1,2,1,2]
size = 4
count = 2,我们想尝试将该组划分为2个子集
subsetTotal =(1 + 2 + 1 + 2)/ 2 = 3
迭代1:
search = {0b:true,1b:true,10b:true,100b:true,1000b:true,101b:true}
nextSearch = {11b:true,1100b:true,110b:true,1001b:true}
迭代2:
search = {11b:true,1100b:true,110b:true,1001b:true,111b:true,1101b:true}
nextSearch = {1111b:true}
最终检查
(1&lt;&lt; size)== 10000b,(1&lt;&lt; size) - 1 == 1111b
由于 nextSearch [1111b] 存在,我们将返回成功。
答案 2 :(得分:-1)
UPD:我把N和K相互混淆了,我的想法是对的,但效率不高。最后添加了有效的想法
假设到目前为止您已经创建了k-1个子集,现在您想要创建第k个子集。要创建第k个子集,您需要能够回答这两个问题:
1-第k个子集的元素总和是什么?
2-到目前为止使用了哪些元素?
回答第一个问题很简单,总和应该等于所有元素之和除以K,我们将其命名为 subSum 。
对于第二个问题,我们需要使用或不使用每个元素的状态。这里我们需要使用位掩码的想法。
这里是dp重现:
dp [i] [mask] =表示可以创建i子集,每个子集的总和等于 subSum ,使用掩码中的1(未使用)元素(在其位中)表示),所以dp [i] [mask]是一个布尔类型。
对于所有可能的 mask2 状态,dp [i] [mask] = OR(dp [i-1] [mask2])。 mask2 将通过将1的掩码转换为0来生成,即我们希望成为第i个子集的元素的那些1。
为了检查所有可能的mask2,你需要检查可用1位的所有2 ^ n个子集。因此,总的来说,时间复杂度将是O(N *(2 ^ n)*(2 ^ N))。你的问题是20 * 2 ^ 8 * 2 ^ 8 = 10 * 2 ^ 17&lt; 10 ^ 7可以通过时限。
显然,对于基本情况,你必须自己处理dp [0] [mask],而不使用recurrence.Final答案是dp [K] [2 ^ N-1]是否为真。
__ UPD __ :为了获得更好的性能,在进入DP之前,您可以使用 subSum 之和预处理所有子集。然后,为了计算mask2,您只需要遍历预处理列表,并查看带掩码的AND操作是否会导致列表中的子集。
<强> UPD2:强> 为了获得有效的解决方案,我们可以使用这样一个事实:在每一步,我们都知道直到那一点的元素总和。所以我们可以逐个添加元素到掩码中,每当我们有一个可被 K 整除的和时,我们就可以进入下一步创建下一个子集。
if(掩码的已用元素总和可被K整除)
dp[i][mask]= dp[i+1][mask];
否则
dp[i][mask]|=dp[i][mask ^(1<<i)] provided that i-th item is not used and can not exceed the current sum more than i*subSum.