第十三届蓝桥杯JavaB组G题--数组切分

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等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值