数组切分
题目分析
动态规划三阶段
第一个阶段定义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[j−1]
而区间[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[j−1]
第三个阶段写代码
(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]);
}
}