Distribute numbers to two “containers” and minimize their difference of sum

it can be solved by Dynamical Programming.Here are some useful link:

Tutorial and Code: http://www.cs.cornell.edu/~wdtseng/icpc/notes/dp3.pdf
A practice: http://people.csail.mit.edu/bdean/6.046/dp/ (then click Balanced Partition)

What‘s more, please note that if the scale of problem is damn large (like you have 5 million numbers etc.), you won‘t want to use DP which needs a too huge matrix. If this is the case, you want to use a kind of Monte Carlo Algorithm:

1. divide n numbers into two groups randomly (or use your method at this step if you like);
2. choose one number from each group,
3. if (to swap these two number decrease the difference of sum) swap them;
4. repeat step 2 until "no swap occurred for a long time".

You don‘t want to expect this method could always work out with the best answer, but it is the only way I know to solve this problem at very large scale within reasonable time and memory.

====== 找出一组数字中, 和为整个数组和的一半(或任意其他值)的子集 =======

Dynamic Programming: Partition

假设数组C[]特别大, n>10000这样, 这需要先计算出和, 假设和为N, 创建一个大小为N+1的boolean数组T[], 全部设为false. 在以下的处理中, 会将可以组合出的值, 比如s, 将T[s]置为true, 如果T[N/2]为true, 则出现符合要求的子集.1. 依次将C[i]与之前产生的T[j]=true的点, 组合成为新的和, 并将其下标的T置为true
2. j循环中, 需要从右往左进行, 以免因T赋值的下标出现到j的右侧, 而导致重复计算

bool T[10240];
bool partition( vector< int > C ) {
    // compute the total sum
    int n = C.size();
    int N = 0;
    for( int i = 0; i < n; i++ ) N += C[i];
    
    // initialize the table
    T[0] = true;
    for( int i = 1; i <= N; i++ ) T[i] = false;
    
    // process the numbers one by one
    for( int i = 0; i < n; i++ )
        for( int j = N ­ C[i]; j >= 0; j­­ )
            if( T[j] ) T[j + C[i]] = true;
        
    return T[N / 2];
}

优化:
1. 不需要每次从最右端开始, 记录每次j的最后值
2. 不需要计算到N, 到N/2就可以, 因为有sum=x的话, N-x也是存在的
3. 将C排序, 这样T的true下标增长会从慢->快

bool T[10240];
bool partition( vector< int > C ) {
    // compute the total sum and sort C
    int n = C.size();
    int N = 0;
    for( int i = 0; i < n; i++ ) N += C[i];
    sort( C.begin(), C.end() );
    
    // initialize the table
    T[0] = true;
    for( int i = 1; i <= N; i++ ) T[i] = false;
    int R = 0;    // rightmost true entry
    
    // process the numbers one by one
    for( int i = 0; i < n; i++ ) {
        for( int j = R; j >= 0; j­­ )
            if( T[j] ) T[j + C[i]] = true;
        R = min( N / 2, R + C[i] );
    }
    return T[N / 2];
}

====== 如果数组每个成员的数量是无限的, 成为一个询问对于某个值, 可能的组合数量的问题 =======

比如如何组合出M这个数字.
这时候将j从左往右遍历就行了, 因为这个T下标会不断重复计算而增长同时, T不再是boolean数组, 而是int数组, 初始化为0, 每次命中, 值都增加1, 最后T[M]的值就是组合数量

int T[10240];
int coins( vector< int > C, int N ) {
    // initialize the table
    T[0] = 1;
    for( int i = 1; i <= N; i++ ) T[i] = 0;
    
    // process the numbers one by one
    for( int i = 0; i < n; i++ )
        for( int j = 0; j + C[i] <= N; j++ )
            T[j + C[i]] += T[j];
    
    return T[N];
}

====== 如果数组成员有重复, 但个数有限, 依旧询问组合数量 =======

比如对于每个C[i], 其数量是D[i], 这时候要引入第三层循环k, 限制循环的次数, 方向也要改为由右至左

bool T[10240];
bool partition( vector< int > C, vector< int > D ) {
    // compute the total sum (value)
    int n = C.size();
    int N = 0;
    for( int i = 0; i < n; i++ ) N += C[i] * D[i];
    
    // initialize the table
    T[0] = true;
    for( int i = 1; i <= N; i++ ) T[i] = false;
    int R = 0;  // rightmost true entry
    // process the numbers one by one
    for( int i = 0; i < n; i++ ) {
        for( int j = R; j >= 0; j­­ ) if( T[j] )
            for( int k = 1; k <= D[i] && j + k * C[i] <= N / 2; k++ )
                T[j + k * C[i]] = true;
        R = min( N / 2, R + C[i] * D[i] );
    }
        
    return T[N / 2];
}

====== 如果问题限制子集的集合大小, 询问组合数量 =======

这时候要做一个二维的表, k by n, k轴是数量, n轴是和, 然后用每行去生成下一行.

====== 对于标题的问题 =======

严格解
假设数组为C[], 其和为N
1. 将C[]排序
2. 构造维数组T[x], x < N+1, 初始化全部为-1, T[0]设为0
3. 从i小到大依次遍历C, 对于每个C[i]
5. 按j从N/2到0依次遍历T[], 对于每个T[j], 如果T[j]>=0, 且T[j+C[i]]<0, 则设T[j+C[i]]为i
6. 完成以上遍历, 寻找离T[N/2]最近的为true的点, 比如是T[M], 则N-2M就是最小的差绝对值.
7. 获取子集: 取T[M]的值a, 依次取T[M - C[a]]的值b, 取T[M - C[a] - C[b]]的值c....得到组合

追求性能的非严格解
假设数组为C[], 设定一个阈值为C[]数组大小, 比如M
1. 将C[]排序
2. 按顺序将C[]均分为C1[]和C2[]两个数组
3. 按相同的顺序, 依次尝试以下三种操作
a) 移动C1[i] 到 C2[]
b) 移动C2[j] 到 C1[]
c) 交换C1[i] 和 C2[j]
如果产生的新C1[]和C2[]其和的差值减小, 则重复本操作
否则i和j依次增加, 重复本次操作
4. 如果在M个上一步操作中, 无变更产生, 则调整完成

时间: 2024-10-13 05:12:29

Distribute numbers to two “containers” and minimize their difference of sum的相关文章

hdu 3117 Fibonacci Numbers

点击此处即可传送到hdu 3117 **Fibonacci Numbers** Problem Description The Fibonacci sequence is the sequence of numbers such that every element is equal to the sum of the two previous elements, except for the first two elements f0 and f1 which are respectively

spoj RAONE - Ra-One Numbers (数位dp)

RAONE - Ra-One Numbers no tags In the War between good and evil . Ra-One is on the evil side and G-One on the good side. Ra-One is fond of destroying cities and its G-one's duty to protect them.. Ra-One loves to destroy cities whose Zip Code has spec

HDU 3117 Fibonacci Numbers(Fibonacci矩阵加速递推+公式)

题目意思很简单:求第n个Fibonacci数,如果超过八位输出前四位和后四位中间输出...,否则直接输出Fibonacci数是多少. 后四位很好求,直接矩阵加速递推对10000取余的结果就是. 前四位搜了一下:http://blog.csdn.net/xieqinghuang/article/details/7789908 Fibonacci的通项公式,对,fibonacci数是有通项公式的-- f(n)=1/sqrt(5)(((1+sqrt(5))/2)^n+((1-sqrt(5))/2)^n

HDU3117 Fibonacci Numbers 【通项公式】

Fibonacci Numbers Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 1936    Accepted Submission(s): 759 Problem Description The Fibonacci sequence is the sequence of numbers such that every eleme

bear and five cards

#include<stdio.h>int main(){    int a[5],flag[2]={1,1},same[2]={0},ans=0;    for(int i=0;i<5;i++)    {        scanf("%d",&a[i]);        ans+=a[i];    }    for(int k=0,i=0;i<5;i++)    {        if(flag[k]>1) k++;        for(int

Codeforces Round #356 (Div. 2) [Codeforces680]

此处有目录↑ Codeforces Round #356(Div. 2):http://codeforces.com/contest/680 A. Bear and Five Cards (贪心) time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standard output A little bear Limak plays a game. He has

C - Bear and Five Cards

Description A little bear Limak plays a game. He has five cards. There is one number written on each card. Each number is a positive integer. Limak can discard (throw out) some cards. His goal is to minimize the sum of numbers written on remaining (n

浅谈linux 中的目标文件 即.o后缀名的文件

实际上 目标文件从结构上讲,已经非常接近可执行文件,只是没有经过链接的过程,所以其中有些符号或者地址还没有被调整. 实际上上在linux下 都是可以称之为ELF文件. 看一下这个图,a.out 就是我们的hello.c的执行文件.hello.o 就是目标文件 所以实际上他们2 几乎是一样的. 一般目标文件有很多属性,比如符号表啊,字符串之类的,然后目标文件 把这些属性 按照 segment的形式存储,也叫段.就是大家经常遇到的段错误 里的那个段. 一般来说程序代码被编译以后 主要分为程序指令和程

[C7] Andrew Ng - Sequence Models

About this Course This course will teach you how to build models for natural language, audio, and other sequence data. Thanks to deep learning, sequence algorithms are working far better than just two years ago, and this is enabling numerous exciting