当前位置:编程学习 > C/C++ >>

编程之美2.18——数组分割

问题:
1. 有一个无序、元素个数为2n的正整数数组,要求:如何能把这个数组分割为两个子数组,子数组的元素个数不限,并使两个子数组之和最接近。
2. 有一个无序、元素个数为2n的正整数数组,要求:如何能把这个数组分割为元素个数为n的两个数组,并使两个子数组之和最接近。

ps: 这只是我对该问题解法的理解,其实感觉还是有点变扭的,如有更好的思考方式,希望告知,谢谢。
1. 解法:
由于对两个子数组和最接近的判断不太直观,我们需要对题目进行适当转化。我们知道当一个子数组之和最接近原数组之和sum的一半时,两个子数组之和是最接近的。所以转化后的题目是:从2n个数中选出任意个数,其和尽量接近于给定值sum/2。

这个问题存储的是从前k个数中选取任意个数,且其和为s的取法是否存在dp[k][s]。之所以将选出的数之和放在下标中,而不是作为dp[k]的值,是因为那种做法不满足动态规划的前提——最优化原理,假设我们找到最优解有k个数(选出的这k个数之和是最接近sum/2的),但在所有k-1数之和中的最优解的前k-1个数之和可能并不是最接近sum/2的,即最优解的子问题并不是最优的,所以不满足最优化原理。因此我们需要将dp[k]的值作为下标存储起来,将这个最优问题转化为判定问题,用带动态规划的思想的递推法来解。

外阶段:在前k1个数中进行选择,k1=1,2...2*n。
内阶段:从这k1个数中任意选出k2个数,k2=1,2...k1。
状态:这k2个数的和为s,s=1,2...sum/2。
决策:决定这k2个数的和有两种决策,一个是这k2个数中包含第k1个数,另一个是不包含第k1个数。
dp[k][s]表示从前k个数中取任意个数,且这些数之和为s的取法是否存在。
[cpp] 
#include <iostream> 
#include <algorithm> 
 
using namespace std; 
 
#define MAXN 101 
#define MAXSUM 100000 
int A[MAXN]; 
bool dp[MAXN][MAXSUM]; 
 
// dp[k][s]表示从前k个数中去任意个数,且这些数之和为s的取法是否存在 
int main() 

    int n, i, k1, k2, s, u; 
    cin >> n; 
    for (i=1; i<=2*n; i++) 
        cin >> A[i]; 
    int sum = 0; 
    for (i=1; i<=2*n; i++) 
        sum += A[i]; 
    memset(dp,0,sizeof(dp)); 
    dp[0][0]=true; 
    // 外阶段k1表示第k1个数,内阶段k2表示选取数的个数 
    for (k1=1; k1<=2*n; k1++)            // 外阶段k1 
    { 
        for (k2=k1; k2>=1; k2--)     // 内阶段k2 
            for (s=1; s<=sum/2; s++) // 状态s 
            { 
                //dp[k1][s] = dp[k1-1][s]; 
                // 有两个决策包含或不包含元素k1 
                if (s>=A[k1] && dp[k2-1][s-A[k1]]) 
                    dp[k2][s] = true; 
            } 
    } 
    // 之前的dp[k][s]表示从前k个数中取任意k个数,经过下面的步骤后 
    // 即表示从前k个数中取任意个数 
    for (k1=2; k1<=2*n; k1++) 
        for (s=1; s<=sum/2; s++) 
            if (dp[k1-1][s]) dp[k1][s]=true; 
    // 确定最接近的给定值sum/2的和 
    for (s=sum/2; s>=1 && !dp[2*n][s]; s--); 
    printf("the differece between two sub array is %d\n", sum-2*s); 

2. 解法:www.zzzyk.com
但本题还增加了一个限制条件,即选出的物体数必须为n,这个条件限制了内阶段k2的取值范围,并且dp[k][s]的含义也发生变化。这里的dp[k][s]表示从前k个数中取任意不超过n的k个数,且这些数之和为s的取法是否存在。
[cpp] 
#include <iostream> 
#include <algorithm> 
 
using namespace std; 
 
#define MAXN 101 
#define MAXSUM 100000 
int A[MAXN]; 
bool dp[MAXN][MAXSUM]; 
 
// 题目可转换为从2n个数中选出n个数,其和尽量接近于给定值sum/2 
int main() 

    int n, i, k1, k2, s, u; 
    cin >> n; 
    for (i=1; i<=2*n; i++) 
        cin >> A[i]; 
    int sum = 0; 
    for (i=1; i<=2*n; i++) 
        sum += A[i]; 
    memset(dp,0,sizeof(dp)); 
    dp[0][0]=true; 
    // 对于dp[k][s]要进行u次决策,由于阶段k的选择受到决策的限制, 
    // 这里决策选择不允许重复,但阶段可以重复,比较特别 
    for (k1=1; k1<=2*n; k1++)                // 外阶段k1 
        for (k2=min(k1,n); k2>=1; k2--)      // 内阶段k2 
            for (s=1; s<=sum/2; s++) // 状态s 
                // 有两个决策包含或不包含元素k1 
                if (s>=A[k1] && dp[k2-1][s-A[k1]]) 
                    dp[k2][s] = true; 
    // 确定最接近的给定值sum/2的和 
    for (s=sum/2; s>=1 && !dp[n][s]; s--); 
    printf("the differece between two sub array is %d\n", sum-2*s); 


作者:linyunzju
补充:软件开发 , C++ ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,