编程之美读书笔记-数组分割

来源:互联网 发布:编程麻将机 编辑:程序博客网 时间:2024/05/24 05:38

题目:有一个没有排序,元素个数为2n的正整数数组。要求把它分割为元素个数为n的两个数组,并使两个子数组的和最接近。
解析:一个直观的想法是先将数组中所有元素排序为a1<a2<a3……<a2N,将它们划分为两个子数组S1=a1,a3,a5……和S2=a2,a4,a6……。从S1和S2中找出一对数进行交换,使得它们之间的差值尽可能小,直到找不到可对换的。这种想法的缺陷是得到的S1和S2并不是最优的。假设2n个整数之和为SUM,从2n个整数中找出n个元素的和,有三种可能:大于SUM/2,等于SUM/2和小于SUM/2,可以只考虑小于等于SUM/2的情况。利用动态规划来解决这个问题。把任务分成2n步,第k步的定义是前k个元素中任意i个元素的和Sk。将第k步分拆为两小步,首先得到前k-1个元素中任意i个元素的和Sk-1,然后令Sk=Sk-1U{vi+arr[k]}。

#include<vector>#include<iostream>#include<algorithm>using namespace std;int n,arr[1000];int main(){cin >> n;for (int i = 1; i <= 2 * n; i++){cin >> arr[i];}int sum = 0;for (int i = 1; i <= 2 * n; i++){sum += arr[i];}vector<int> sums[100];//sums[i]表示从arr中取i个数所能产生的和的集合vector<int>::iterator it;sums[0].push_back(0);int i, j, i_max;for (i = 1; i <= 2 * n; i++){i_max = min(i - 1, n - 1);for (j = i_max; j >= 0; j--){for (it = sums[j].begin(); it != sums[j].end(); it++){sums[j + 1].push_back(arr[i] + *it);}}}int minval = sum;for (it = sums[n].begin(); it != sums[n].end(); it++){if (*it <= sum / 2){minval = min(minval, sum - *it*2);}}cout << minval << endl;return 0;}

这个算法的复杂度是指数级的,因此在n很大时效率很低。我们需要设计一个算法使得第k步所花费的时间与k无关。原来是给定Sk-1={vi}求Sk,那么也可以给定Sk的可能值v和arr[k],去寻找v-arr[k]是否在Sk-1={vi}中。

#include<vector>#include<iostream>#include<algorithm>using namespace std;int n,arr[1000],isok[100][1000];//isok[i][v]表示是否可以找到i个数使得它们和等于vint main(){cin >> n;for (int i = 1; i <= 2 * n; i++){cin >> arr[i];}int sum = 0;for (int i = 1; i <= 2 * n; i++){sum += arr[i];}isok[0][0] = 1;for (int k = 1; k <= 2 * n; k++){for (int i = min(k, n); i >= 1; i--){for (int v = 1; v <= sum / 2; v++){if (v >= arr[k] && isok[i - 1][v - arr[k]]){isok[i][v] = true;}}}}for (int v = sum / 2; v >= 0; v--){if (isok[n][v]){cout << sum - 2 * v << endl;break;}}return 0;}



0 0
原创粉丝点击