- 每个集合中的数字总数最多相差1。
- A集合中所有数字的总和应尽可能接近B集合中所有数字的总和,即分配应该是公平的。
谢谢。
由于数字很小,它不是NP完全问题。
要解决它,您可以使用动态规划:
创建一个三维布尔表格,其中t[s, n, i]为true表示可以通过i下标以下的n个元素的子集达到和为s。要计算t[s, n, i]的值,请检查t[s, n, i-1]和t[s-a[i], n-1, i-1]。然后在第二个索引n/2中查找最佳解决方案。
编辑:实际上,您不需要一次性获取完整的表格。您可以为每个索引i创建一个二维表格t_i[s, n],并从i-1的表格计算i的表格,因此您只需要两个这些二维表格,这可以节省大量内存。(感谢Martin Hock。)
证明:
在每个4个数字的倍数被分配之后,A和B都包含相同数量的物品,并且每组中的物品总和相同,因为
(n) + (n - 3) == (n - 1) + (n - 2)
首先,不考虑第一个限制条件(即尽可能使总和接近),找到问题的解决方案。可以使用DP方法解决这个问题(您可以在此处详细了解DP,而第一个与硬币有关的问题与您的问题非常相似)。
一旦您能够解决问题,就可以将另一个状态添加到您的DP中——已选择进入子集的人数。这将给出N^3的算法。
第三步:
n = 2,数字为110, 110
n < 3,所以需要分配数字:
A = 25, 10, 75
B = 20, 30, 15, 45
我已经测试过,这在每种情况下都可以正常工作。
@ShreevatsaR指出下面的算法被称为贪心算法。它在处理某些输入时表现不佳(我尝试了10组随机生成的大小为100的输入集,在所有情况下,总和非常接近,这让我认为对输入进行排序就足以使该算法成功)。
另请参见{{link1:“最简单的困难问题”,《美国科学家》杂志,2002年3-4月号}},由ShreevatsaR推荐。
#!/usr/bin/perl
use strict;
use warnings;
use List::Util qw( sum );
my @numbers = generate_list();
print "@numbers\n\n";
my (@A, @B);
my $N = @numbers;
while ( @numbers ) {
my $n = pop @numbers;
printf "Step: %d\n", $N - @numbers;
{
no warnings 'uninitialized';
if ( sum(@A) < sum(@B) ) {
push @A, $n;
}
else {
push @B, $n;
}
printf "A: %s\n\tsum: %d\n\tnum elements: %d\n",
"@A", sum(@A), scalar @A;
printf "B: %s\n\tsum: %d\n\tnum elements: %d\n\n",
"@B", sum(@B), scalar @B;
}
}
sub generate_list { grep { rand > 0.8 } 1 .. 450 }
generate_list
返回一个按升序排列的列表。你在第二点的要求需要澄清,因为:“A中所有数字的总和尽可能接近B中所有数字的总和”是清晰明了的,但是你的陈述“分配应该公平”让一切变得不清楚。'公平'到底意味着什么?这个过程需要一个随机元素吗?
#include <stdio.h>
#include <stdlib.h>
#define MAXPAR 50
#define MAXTRIES 10000000
int data1[] = {192,130,446,328,40,174,218,31,59,234,26,365,253,11,198,98,
279,6,276,72,219,15,192,289,289,191,244,62,443,431,363,10
} ;
int data2[] = { 1,2,3,4,5,6,7,8,9 } ;
// What does the set sum to
int sumSet ( int data[], int len )
{
int result = 0 ;
for ( int i=0; i < len; ++i )
result += data[i] ;
return result ;
}
// Print out a set
void printSet ( int data[], int len )
{
for ( int i=0; i < len; ++i )
printf ( "%d ", data[i] ) ;
printf ( " Sums to %d\n", sumSet ( data,len ) ) ;
}
// Partition the values using simulated annealing
void partition ( int data[], size_t len )
{
int set1[MAXPAR] = {0} ; // Parttition 1
int set2[MAXPAR] = {0} ; // Parttition 2
int set1Pos, set2Pos, dataPos, set1Len, set2Len ; // Data about the partitions
int minDiff ; // The best solution found so far
int sum1, sum2, diff ;
int tries = MAXTRIES ; // Don't loop for ever
set1Len = set2Len = -1 ;
dataPos = 0 ;
// Initialize the two partitions
while ( dataPos < len )
{
set1[++set1Len] = data[dataPos++] ;
if ( dataPos < len )
set2[++set2Len] = data[dataPos++] ;
}
// Very primitive simulated annealing solution
sum1 = sumSet ( set1, set1Len ) ;
sum2 = sumSet ( set2, set2Len ) ;
diff = sum1 - sum2 ; // The initial difference - we want to minimize this
minDiff = sum1 + sum2 ;
printf ( "Initial diff is %d\n", diff ) ;
// Loop until a solution is found or all are tries are exhausted
while ( diff != 0 && tries > 0 )
{
// Look for swaps that improves the difference
int newDiff, newSum1, newSum2 ;
set1Pos = rand() % set1Len ;
set2Pos = rand() % set2Len ;
newSum1 = sum1 - set1[set1Pos] + set2[set2Pos] ;
newSum2 = sum2 + set1[set1Pos] - set2[set2Pos] ;
newDiff = newSum1 - newSum2 ;
if ( abs ( newDiff ) < abs ( diff ) || // Is this a better solution?
tries/100 > rand() % MAXTRIES ) // Or shall we just swap anyway - chance of swap decreases as tries reduces
{
int tmp = set1[set1Pos] ;
set1[set1Pos] = set2[set2Pos] ;
set2[set2Pos] = tmp ;
diff = newDiff ;
sum1 = newSum1 ;
sum2 = newSum2 ;
// Print it out if its the best we have seen so far
if ( abs ( diff ) < abs ( minDiff ) )
{
minDiff = diff ;
printSet ( set1, set1Len ) ;
printSet ( set2, set2Len ) ;
printf ( "diff of %d\n\n", abs ( diff ) ) ;
}
}
--tries ;
}
printf ( "done\n" ) ;
}
int main ( int argc, char **argv )
{
// Change this to init rand from the clock say if you don't want the same
// results repoduced evert time!
srand ( 12345 ) ;
partition ( data1, 31 ) ;
partition ( data2, 9 ) ;
return 0;
}
我假设这些数字不是连续的,而且你不能重新平衡?
由于限制1,你每隔一个插入就需要交换桶。所以每当你不被迫选择一个桶时,选择一个逻辑桶(添加该数字会使总和更接近另一个桶)。如果这个桶不是你先前选择的桶,则你可以再选择一次而不被迫。