1.数组切分
题目链接:数组切分
2.明确dp[数组]
第一眼回溯法枚举确实是可以解决该题,但是会超时这里不描述该方法的代码,重点讲述动态规划的思路,改题目是要找划分的连续子序列的个数,所以我们明确一个dp数组的含义是:dp[i]表述以A[i]结尾可以划分多少个连续子序列,故最后求得的dp[n]就是我们要求的答案
3dp初始化
因为我们的数组A是从下标1开始输入的,那dp[0]是否就无意义了呢?当然不是,dp[0]对整个dp数组初始化有重要作用,可以这么想假设A={1}很明显dp[1]=1,因为dp[1]是由dp[0]推导而来,因而先将dp[0]初始化为1,下面的状态转移会再次对dp[0]解释
4.状态转移方程
假设A={1,2,3,4},dp[1]=1,dp[2]=2,dp[3]=4,都是比较容易在脑海里就模拟出来的,我们如何用这三个推导出dp[4]呢。
因为dp[3]是前三个数的切分方法和,因为数目较少可以直接列出来看dp[3]:{{1},{2},{3}},{1,{2,3}},{{1,2},3},{1,2,3}}=4,然后我们要插入子序列{4},因为4为一个数因而一定为连续自然数,我们可以把4插入到每一个dp[3]的可能的末尾因而有{{1},{2},{3},{4}},{1,{2,3},4},{{1,2},3,4},{1,2,3,4}}4种可能所以dp[4]+=dp[3]先加上这四种可能。
设立两个索引i,j表示j到i结尾的子序列,很显然刚才i,j都指向在4,现在j--,j指向3,子序列{3,4}很明显也是一段递增子序列,{3,4}不拆因为拆3拆4的结果我们已经计算过,就是dp[3]的所有可能后加一个{4},我们看[0-(j-1)]这一段子序列能拆分成多少个子序列,很显然就是dp[2],我们可以列出dp[2]的所有可能{(1,2) {(1),(2)}}两种可能同样的我们可以将{3,4}添加到每一个的末尾有两种可能所以dp[4]+=dp[2],同理{2,3,4}是连续递增dp[4]在加上dp[1],最后当j=1时整个数组{1,2,3,4}也是连续的,
dp[4]+=dp[0],可见dp[0]必须设为1结果才能正确,可得dp[4]=dp[3]+dp[2]+dp[1]+dp[0]=4+2+1+1=8;
因而得知dp[i]可由i到0有多少段连续子序列推断出来,并且dp[4]可以有前三个dp推出,dp[3],dp[2],dp[1]同样可以,如果是连续子序列则加上dp[j-1],判断是不是连续子序列的方法为,如若i到j的最大值:max-min==i-j则该序列为连续子序列如{3,5,4}max-min=2,i-j=2为连续自然数序列故dp状态转移代码为
for(int i=1;i<=n;i++)
{
int max=0;
int min=Integer.MAX_VALUE;
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]=add(dp[i],dp[j-1]);//相当于dp[i]+dp[j-1]
}
}
}
初始时i=j相当于让dp[i]继承dp[i-1]的值可以理解为将这一个数添加到dp[i-1]的所有可能末尾,因为dp[1]=dp[0]故dp[0]要初始化为1,并且即便末尾两个数不是连续子序列也一定会初始化dp[i]的值为dp[i-1]
5.可以运行的代码+总结
import java.io.*;
public class test3 {
static int mod=1000000007 ;
static StreamTokenizer st=new StreamTokenizer(new BufferedInputStream(System.in));//快读
static PrintWriter pw=new PrintWriter(new OutputStreamWriter(System.out));//快写
public static void main(String[] args) throws IOException {
int n=I();
int []a=new int[n+1];
for(int i=1;i<=n;i++)
a[i]=I();
int dp[]=new int[n+1];//dp[i]表述第i个结尾的数组有多少种切分方法
dp[0]=1;//初始化dp数组
for(int i=1;i<=n;i++)
{
int max=0;
int min=Integer.MAX_VALUE;
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]=add(dp[i],dp[j-1]);//相当于dp[i]+dp[j-1]
}
}
}
System.out.println(dp[n]);
}
public static int add(int a,int b)
{
if(a>mod)
a-=mod;
if(b>mod)
b-=mod;//比%效率高
return (a+b)%mod;
}
public static int I() throws IOException {
st.nextToken();
return (int)st.nval;
}
}
因为题目中要值要取模add函数同时实现了相加和取模,另外在程序竞赛中熟知的Scanner的输入太慢了,如果有大数据量要输入可能会超时,因而要掌握快读的方法如StreamTokenizer等