数组切分 动态规划 递归

hackerank 算法->动态规划->Nikita and the Game
https://www.hackerrank.com/challenges/array-splitting

Nikita just came up with a new array game. The rules are as follows:

Initially, there is an array, , containing N integers.

In each move, Nikita must partition the array into non-empty parts such that the sum of the elements in the left partition is equal to the sum of the elements in the right partition. If Nikita can make such a move, she gets point; otherwise, the game ends.

After each successful move, Nikita discards either the left partition or the right partition and continues playing by using the remaining partition as array .

Nikita loves this game and wants your help getting the best score possible. Given , can you find and print the maximum number of points she can score?

这里写图片描述

1.刻画一个最优解的结构特征

如果一个序列A可以存在一个最优切分,切分序列A一次只能得到一分,所以序列A的最优切分由其切分的子序列的最优切分决定。切分序列A的最大分数等于其子序列切分的最大分数加1。

2.递归地定义最优解的值

一个序列A,其最优切分得到的子序列B和C。
scoreA = max{scoreB, scoreC} + 1

× 3.自底向上
->自顶向下

由于自顶向下分解子问题比较直观,所以选择了这种方法。

递归的结束条件是,子序列的长度为1,即只包含一个元素不能再切分,得分为0。

递归对于序列的处理是,将序列按照规则尽可能多的尝试各种切割。考虑序列[p…q],切割点p<=k<=q, p<=i<=q, p<=j<=q, i <= j。假设存在两个切割点使得左子序列和右子序列的和相等,则必定有i = j 或者 [i+1, j]全为0(这道题目有一个约束就是序列的值全为非负)。对于全0子子序列的切割不管在左子序列还是右子序列都是一样的,因为最终该子子序列和邻接的正数子子序列构成的序列必定会无法切割,对于最终的结果没有影响。

即如果序列存在一个切割,则该切割一定为最优切割。

还有一个小trick可以减少递归的次数,就是当序列的和为奇数的时候,必定不能将序列切割成左右子序列和相等。

为了避免重复计算序列的和,所以用一个记录数组sumRec来记录和。sumRec[i]表示序列[1…i]的和。要求序列[p..q]的和,则用sumRec[q] - sumRec[p-1]。

因为前面的结论 即如果序列存在一个切割,则该切割一定为最优切割。,所以该问题简化后是一个不带有重叠子问题的问题。不需要另外存储一个score数组来记录已经计算过的子序列的最优解。

代码如下

#include <cmath>
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
vector<unsigned long>sumRec;
vector<unsigned long> arr;

unsigned long max(unsigned long a, unsigned long b){
    return a > b ? a : b; 
}

unsigned long calScore(long p, long q){
    //empty sequence
    if(q - p < 1){
        return 0;
    }
    unsigned long arrSum = sumRec[q] - sumRec[p-1];
    //cannot split into two part with equal int sum
    if(arrSum % 2 != 0){
        return 0;
    }
    arrSum = arrSum >> 1;  // arrSum / 2
    unsigned long scoreLeft = 0, scoreRight = 0;
    for(long k = p; k < q; k++){
        if( (sumRec[k] - sumRec[p-1]) == arrSum ){  //if sum[p,k] = sum[k+1,q], then split it
            scoreLeft = calScore(p, k);   //calculate left part score
            scoreRight = calScore(k+1, q); //calculate right part score
            return max(scoreLeft, scoreRight) + 1;
        }
    }
    //cannot split the sequence
    return 0;
}

int main() {
    /* Enter your code here. Read input from STDIN. Print output to STDOUT */   
    int testcase;
    scanf("%d", &testcase);
    unsigned long number, result, sum;
    long len;
    while(testcase--){
        scanf("%lu", &len);
        sum = 0;
        arr.push_back(0);
        sumRec.push_back(0);
        for(int i=0;i<len;i++){
            scanf("%lu", &number);
            sum += number;
            arr.push_back(number);
            sumRec.push_back(sum);
        }
        result = calScore(long(1), long(len));
        printf("%ld\n", result);
        arr.clear();
        sumRec.clear();
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值