题目:
已知一个长度为 N 的数组:A1,A2,A3,…AN 恰好是 1∼N 的一个排列。现 在要求你将 A 数组切分成若干个 (最少一个, 最多 N 个) 连续的子数组, 并且 每个子数组中包含的整数恰好可以组成一段连续的自然数。
例如对于 A=1,3,2,4, 一共有 5 种切分方法:
1324 : 每个单独的数显然是 (长度为 1 的) 一段连续的自然数。
{1}{3,2}{4}:{3,2} 包含 2 到 3 , 是 一段连续的自然数, 另外 1 和 4 显然 也是。
{1}{3,2,4}:{3,2,4} 包含 2 到 4 , 是一段连续的自然数, 另外1 显然也是。
{1,3,2}{4}:{1,3,2} 包含 1 到 3 , 是 一段连续的自然数, 另外 4 显然也是。
{1,3,2,4} : 只有一个子数组, 包含 1 到 4 , 是 一段连续的自然数。
输入格式
第一行包含一个整数 N 。第二行包含 N 个整数, 代表 A 数组。
输出格式
输出一个整数表示答案。由于答案可能很大, 所以输出其对 1000000007 取 模后的值
样例输入
4
1 3 2 4
样例输出
5
评测用例规模与约定
对于 30% 评测用例,1≤N≤20.
对于100% 评测用例,1≤N≤10000.
运行限制
- 最大运行时间:5s
- 最大运行内存: 512M
代码:
import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改
public class Main {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int n=sc.nextInt(); //储存元素个数
int [] A=new int[n+1];
for (int i=1;i<n+1;i++){
A[i]=sc.nextInt();
}
int[] dp=new int[n+1]; //前n个元素之前有几种切分方法
dp[0]=1; //初始化
for (int r=1;r<n+1;r++){
int max=Integer.MIN_VALUE;
int min=Integer.MAX_VALUE;
for (int l=r;l>0;l--){ //从后往前遍历
max=Math.max(A[l],max);
min=Math.min(A[l],min);
if (max-min==r-l){
dp[r]=(dp[r]+dp[l-1])%1000000007; //状态转移
}
}
}
System.out.println(dp[n]);
sc.close();
}
}
思路:本题采用了动态规划的思路。
首先从题目中得知划分出来的数组是连续的,所以一段数组中的max-min就应该等于数组中最右端的下标减去最左端的下表。
max-min=r-l
接着我们从数组的第一个元素开始找前i个元素,有几种划分方式。依次向后dp。得到状态转移方程
dp[r]=dp[r]+dp[l-1]
dp要有两个循环,第一层循环相当于确定了最右边的元素,然后依次向左遍历,找到可以组成连续子序列时再加上最左边元素之前所有元素的划分方式。依次向前直到第一个元素。
第二层循环必须从后往前遍历,因为我们要找最大最小值(从后往前一个数一个数找,可以保证找到的最值一定正确),如果从前往后遍历(拿第一次也就是遍历第一个元素时)最大最小值都是第一个元素,但此时的子序列是从第一个元素到r中间元素都没有进行比较,就会出现错误。
dp[0]=1,我是这么理解的:当后面前n个元素可以组成一个连续子序列时,前面有0个元素只有自身一种情况。比如例题中 1 3 2 4 前3中所有元素可以组成一个连续子序列,没有剩余元素了所以只有自身这一种情况。 也就是说没有元素也是合法情况。