寻找2个相等的和子序列,最大总和?

时间:2018-06-16 19:32:16

标签: c++ algorithm dynamic-programming

  

我删除了这个问题的所有故事情节。

     

Q.你有N个号码。你必须找到2个相等的和子序列,最大总和。您不一定需要使用所有数字。

例如1: -

5
1 2 3 4 1

Sub-sequence 1 : 2 3 // sum = 5
Sub-sequence 2 : 4 1 // sum = 5

Possible Sub-sequences with equal sum are 
{1,2} {3}   // sum = 3
{1,3} {4}   // sum = 4
{2,3} {4,1} // sum = 5

Out of which 5 is the maximum sum.

例如2: -

6
1 2 4 5 9 1

Sub-sequence 1 : 2 4 5   // sum = 11
Sub-sequence 2 : 1 9 1   // sum = 11
The maximum sum you can get is 11

约束:

5 <= N <= 50

1<= number <=1000

sum of all numbers is <= 1000

Important: Only <iostream> can be used. No STLs.

N numbers are unsorted.

If array is not possible to split, print 0.

Number of function stacks is limited. ie your recursive/memoization solution won't work.

方法1:

我尝试了类似下面的递归方法:

#include <iostream>
using namespace std;

bool visited[51][1001][1001];
int arr[51];
int max_height=0;
int max_height_idx=0;
int N;

void recurse( int idx, int sum_left, int sum_right){
    if(sum_left == sum_right){
        if(sum_left > max_height){
            max_height = sum_left;
            max_height_idx = idx;
        }
    }


    if(idx>N-1)return ;

    if(visited[idx][sum_left][sum_right]) return ;

    recurse( idx+1, sum_left+arr[idx], sum_right);
    recurse( idx+1, sum_left         , sum_right+arr[idx]);
    recurse( idx+1, sum_left         , sum_right);

    visited[idx][sum_left][sum_right]=true;

    /*
       We could reduce the function calls, by check the visited condition before calling the function.
       This could reduce stack allocations for function calls. For simplicity I have not checking those conditions before function calls.
       Anyways, this recursive solution would get time out. No matter how you optimize it.
       Btw, there are T testcases. For simplicity, removed that constraint.
    */
}

int main(){
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);

    cin>>N;
    for(int i=0; i<N; i++)
        cin>>arr[i];

    recurse(0,0,0);

    cout<< max_height <<"\n";
}

NOTE:通过测试用例。但是超时。

方法2:

I also tried, taking advantage of constraints.

Every number has 3 possible choice:
    1. Be in sub-sequence 1
    2. Be in sub-sequence 2
    3. Be in neither of these sub-sequences 

So
    1. Be in sub-sequence 1 -> sum +  1*number
    2. Be in sub-sequence 2 -> sum + -1*number
    3. None             -> sum

Maximum sum is in range -1000 to 1000. 
So dp[51][2002] could be used to save the maximum positive sum achieved so far (ie till idx).

CODE:

#include <iostream>
using namespace std;

int arr[51];
int N;
int dp[51][2002];

int max3(int a, int b, int c){
    return max(a,max(b,c));
}
int max4(int a, int b, int c, int d){
    return max(max(a,b),max(c,d));
}

int recurse( int idx, int sum){

    if(sum==0){
        // should i perform anything here?
    }

    if(idx>N-1){
        return 0;
    }

    if( dp[idx][sum+1000] ){
        return dp[idx][sum+1000];
    }

    return dp[idx][sum+1000] = max3 (
                                arr[idx] + recurse( idx+1, sum + arr[idx]),
                                    0    + recurse( idx+1, sum - arr[idx]),
                                    0    + recurse( idx+1, sum           )
                               )  ;

    /*
        This gives me a wrong output.

        4
        1 3 5 4
    */
}


int main(){
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);

    cin>>N;
    for(int i=0; i<N; i++)
        cin>>arr[i];

    cout<< recurse(0,0) <<"\n";

}

上面的代码给出了错误的答案。请帮助我解决/纠正这个记忆。

也可以采用相同的迭代方法。

3 个答案:

答案 0 :(得分:4)

你的第二种方法的想法是正确的,它基本上是减少到the knapsack problem。但是,您的代码似乎缺少明确的合同recurse函数应该执行的操作。

以下是我的建议:int recurse(int idx, int sum)将位置idx..n-1上的元素分配到三个多字符ABC,以便sum+sum(A)-sum(B)=0和尽可能返回最小sum(A)-inf(此处-inf是一些硬编码常量,用作&#34;标记&#34;没有答案;对它有一些限制,我建议-inf == -1000)。

现在,您要使用该合同编写递归回溯,然后添加memoization。瞧,你有一个动态的编程解决方案。

在递归回溯中,我们有两种截然不同的情况:

  1. 没有更多元素可供分发,无法做出选择:idx == n。在这种情况下,我们应检查我们的条件是否成立(sum + sum(A) - sum(B) == 0,即sum == 0)并返回答案。如果sum == 0,则答案为0.但是,如果sum != 0,则没有答案,我们应该返回一些永远不会被选为答案的内容,除非对整个问题没有答案。当我们修改recurse的返回值并且不想要额外的if时,它不能简单地为零甚至-1;它应该是一个数字,当被修改时,仍然是&#34;有史以来最糟糕的答案&#34;。我们可以做的最大修改是将所有数字添加到结果值中,因此我们应该选择小于或等于负最大数字总和的东西(即-1000),因为现有的答案总是严格为正,而那个虚构的答案永远都是非正面的。
  2. 至少有一个剩余元素应分发到ABC。做出选择并从三个选项中选择最佳答案。答案是递归计算的。
  3. 这是我的实施:

    const int MAXN = 50;
    const int MAXSUM = 1000;
    
    bool visited[MAXN + 1][2 * MAXSUM + 1]; // should be filled with false
    int dp[MAXN + 1][2 * MAXSUM + 1]; // initial values do not matter
    
    int recurse(int idx, int sum){
        // Memoization.
        if (visited[idx][sum + MAXSUM]) {
            return dp[idx][sum + MAXSUM];
        }
        // Mark the current state as visited in the beginning,
        // it's ok to do before actually computing it as we're
        // not expect to visit it while computing.
        visited[idx][sum + MAXSUM] = true;
    
        int &answer = dp[idx][sum + MAXSUM];
    
        // Backtracking search follows.
        answer = -MAXSUM;  // "Answer does not exist" marker.
    
        if (idx == N) {
            // No more choices to make.
            if (sum == 0) {
                answer = 0;  // Answer exists.
            } else {
                // Do nothing, there is no answer.
            }
        } else {
            // Option 1. Current elemnt goes to A.
            answer = max(answer, arr[idx] + recurse(idx + 1, sum + arr[idx]));
            // Option 2. Current element goes to B.
            answer = max(answer, recurse(idx + 1, sum - arr[idx]));
            // Option 3. Current element goes to C.
            answer = max(answer, recurse(idx + 1, sum));
        }
        return answer;
    }
    

答案 1 :(得分:0)

状态未在方法1中更新。更改递归的最后一行

visited[idx][sum_left][sum_right];

visited[idx][sum_left][sum_right] = 1;

在从main调用recurse之前,还将被访问的数组memset为false。

答案 2 :(得分:0)

根据Codeforces用户lemelisk here提出的想法,这是一个简单的基于动态编程的解决方案,适合感兴趣的任何人。完成帖子here。我还没有完全测试这段代码。

#include <iostream>
using namespace std;

#define MAXN 20 // maximum length of array
#define MAXSUM 500 // maximum sum of all elements in array
#define DIFFSIZE (2*MAXSUM + 9) // possible size of differences array (-maxsum, maxsum) + some extra

int dp[MAXN][DIFFSIZE] = { 0 };
int visited[DIFFSIZE] = { 0 }; // visited[diff] == 1 if the difference 'diff' can be reached 
int offset = MAXSUM + 1; // offset so that indices in dp table don't become negative
// 'diff' replaced by 'offset + diff' below everywhere

int max(int a, int b) {
    return (a > b) ? a : b;
}
int max_3(int a, int b, int c) {
    return max(a, max(b, c));
}

int main() {
    int a[] = { 1, 2, 3, 4, 6, 7, 5};
    int n = sizeof(a) / sizeof(a[0]);
    int *arr = new int[n + 1];
    int sum = 0;
    for (int i = 1; i <= n; i++) {
        arr[i] = a[i - 1]; // 'arr' same as 'a' but with 1-indexing for simplicity
        sum += arr[i];
    } // 'sum' holds sum of all elements of array

    for (int i = 0; i < MAXN; i++) {
        for (int j = 0; j < DIFFSIZE; j++)
            dp[i][j] = INT_MIN;
    }

    /*
    dp[i][j] signifies the maximum value X that can be reached till index 'i' in array such that diff between the two sets is 'j'
    In other words, the highest sum subsets reached till index 'i' have the sums {X , X + diff}
    See http://codeforces.com/blog/entry/54259 for details
    */

    // 1 ... i : (X, X + diff) can be reached by 1 ... i-1 : (X - a[i], X + diff)
    dp[0][offset] = 0; // subset sum is 0 for null set, difference = 0 between subsets
    visited[offset] = 1; // initially zero diff reached

    for (int i = 1; i <= n; i++) {
        for (int diff = (-1)*sum; diff <= sum; diff++) {
            if (visited[offset + diff + arr[i]] || visited[offset + diff - arr[i]] || visited[offset + diff]) { 
                // if difference 'diff' is reachable, then only update, else no need
                dp[i][offset + diff] = max_3 
                (
                    dp[i - 1][offset + diff], 
                    dp[i - 1][offset + diff + arr[i]] + arr[i], 
                    dp[i - 1][offset + diff - arr[i]] 
                );
                visited[offset + diff] = 1;
            }
        }
        /*
        dp[i][diff] = max {
        dp[i - 1][diff] : not taking a[i] in either subset
        dp[i - 1][diff + arr[i]] + arr[i] : putting arr[i] in first set, thus reducing difference to 'diff', increasing X to X + arr[i]
        dp[i - 1][diff - arr[i]] : putting arr[i] in second set
        initialization: dp[0][0] = 0
        */

        // O(N*SUM) algorithm
    }
    cout << dp[n][offset] << "\n";
    return 0;
}

输出:

14