第13届蓝桥杯javaB组——数组切分

数组切分

题目分析

动态规划三阶段

第一个阶段定义dp数组

(1)缩小规模。规模为当前的数组个数,以及切分的位置。

(2)考虑限制。分割出来的每一部分都是连续的自然数,其实就是数组下标[i,j]中数的最大值和最小值之差恰好是下标i和j之差。

(3)定义dp数组。这里考虑类似最长上升子序列的定义方法。dp[i]表示最后一次分割是以a[i]结尾的方案数。

第二个阶段推导状态转移方程

我们需要找前一次分割是在哪个地方结尾的,假设是j,则转移方程为

d p [ i ] + = d p [ j − 1 ] dp[i]+=dp[j-1] dp[i]+=dp[j1]

而区间[j,i]要满足i和j之差等于a[j]到a[i]之间最大值和最小值之差。以i结尾表示在i的右边切割,当需要把j到i的数组放一起时,应该是在j-1的右边进行切割,所以是 d p [ i ] + = d p [ j − 1 ] dp[i]+=dp[j-1] dp[i]+=dp[j1]

第三个阶段写代码

(1)dp数组的初始化。求方案数,那么最初状态应该是1,因为递推方程里都是dp值相加,如果dp值一开始都是0,那么无论怎么加都是0,所以dp[0]=1。

疑问:为什么不是dp[1]=1呢?

回答:这里的dp[0]=1,可以看做在第一个数的左边进行切割。就以样例为例,1 3 2 4。如果dp[1]=1,那么执行的过程是这样的。

i=2,j=2符合要求,那么切割如下 [ 1 ] [ 3 ] [1][3] [1][3],也就是dp[2]+=dp[1]=1。

​ j=1不符合要求不切割。

i=3,j=3符合要求,即dp[3]+=dp[2]=1,切割如下 [ 1 ] [ 3 ] [ 2 ] [1][3][2] [1][3][2]

​ j=2符合要求,即dp[3]=dp[3]+dp[1]=1+1=2,切割如下 [ 1 ] [ 2 , 3 ] [1][2,3] [1][2,3]

​ j=1符合要求,即dp[3]=dp[3]+dp[0]=2+0=2,切割如下 [ 1 , 2 , 3 ] [1,2,3] [1,2,3],但是因为一开始dp[0]=0,所以这个方案没有被加上。

后面就不写了,主要展示dp[0]=1的作用。dp[i]表示最后一次切割是以a[i]结尾的方案数,也就是在i的右边切一刀,如果在左边切就是以a[i-1]结尾了。

如果最初的状态是dp[1],也就是在a[1]的右边切一刀,那么说明第一个一定被切出去了,就不存在第一个没有切出去的情况,也就是例子里的 [ 1 , 2 , 3 ] [1,2,3] [1,2,3]。dp[0]表示在a[0]的右边也就是a[1]的左边切一刀,这样a[1]就没有被切割出去。

其实这也回到了找最初的状态的问题,明显dp[1]不是最初的状态,而dp[0]是,即啥也没有切割的状态。

(2)递推dp数组。

a.第一层for循环表示的是规模 i 1:n

b.第二层for循环寻找i左边的合法端点j,在寻找的过程中要记录i到j的最小值和最大值

for (int j = i; j > 0; j--) {
    max = Math.max(a[j], max);
    min = Math.min(a[j], min);
    if(max-min==i-j) {
        dp[i] = (dp[i] + dp[j-1])%mod;
    }
}

(3)表示答案。 d p [ n ] dp[n] dp[n]

题目代码
import java.util.Scanner;
public class Main {
    /*
     * 符合要求的子数组中的数据有什么特征
     * ai....aj  max min max-min=j-i 1 2 3 4 min=1 max=4 4-1=4-1 d=1
     * max-min 
     * 
     * dp[i] 从1-i个数据中共有dp[i]种切分方法
     * 状态的转移 dp[i]
     * for i
     *   for j
     *     max-min=i-j
     *     dp[i] += dp[j-1] mod
     */
public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    int n = scanner.nextInt();
    int a[] = new int[n+1];
    int mod = 1000000007;
    for (int i = 1; i < a.length; i++) {
        a[i] = scanner.nextInt();
    }
    int dp[] = new int[n+1];
    dp[0] = 1;//为什么dp0=1?
    //dp[1] = 1;
    for (int i = 1; i < dp.length; i++) {
        int max = a[i];
        int min = a[i];
        for (int j = i; j > 0; j--) {
            max = Math.max(a[j], max);
            min = Math.min(a[j], min);
            if(max-min==i-j) {
                dp[i] = (dp[i] + dp[j-1])%mod;
            }
        }
    }
    System.out.println(dp[n]);
}
}
  • 17
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值