【序列dp】最长上升子序列(二)

文章介绍了使用动态规划解决序列相关问题的思路,包括最长上升子序列的求解,以及在拦截导弹问题中的应用,讨论了如何找到最长下降子序列并计算能覆盖整个序列的子序列数量。此外,还探讨了导弹防御系统问题的解决方案,即如何用最少的上升和下降子序列覆盖序列,并展示了最长公共上升子序列的优化算法,降低了时间复杂度。
摘要由CSDN通过智能技术生成

最长上升子序列-序列dp

  • 什么是序列相关的 DP ?
  • 序列相关 DP,顾名思义,就是将动态规划算法用于数组或者字符串上,进行相关具体问题的求解
  • 何时可以使用序列相关的 DP?
  • 当题目求解以下内容时,可以考虑使用序列相关的 DP
  • 给定两个字符串,求最长/大的某种值
  • 给定数组,求最长/大的某种值

此外,在使用序列相关的 DP 的时候,我们还需要注意到,这是一类的动态规划,所以需要满足动态规划的两种重要条件
最长上升子序列问题是一个经典的动态规划问题,目标是在给定序列中找到一个最长的升序子序列。

1016 最大上升子序列和

在这里插入图片描述

在这里插入图片描述

package acwing_plus.动规.最长上升子序列;

import java.util.Scanner;

/**
 * @author ty
 * @create 2023-03-31 10:00
 */
public class 最长上升子序列和 {
    static int N = 1010;
    static int[] a = new int[N];
    static int[] f = new int[N];
    static int n;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        for (int i = 1;i <= n;i++) {
            a[i] = sc.nextInt();
        }
        for (int i = 1;i <= n;i++) {
            f[i] = a[i];
            for (int j = 1;j < i;j++) {
                if (a[i] > a[j]) {
                    f[i] = Math.max(f[i],f[j] + a[i]);
                }
            }
        }
        int res = 0;
        for (int i = 1;i <= n;i++) {
            res = Math.max(res,f[i]);
        }
        System.out.println(res);
    }
}

1010. 拦截导弹

在这里插入图片描述

第一问:最长下降子序列

第二问:多少个最长下降子序列能覆盖整个序列

在这里插入图片描述

package acwing_plus.动规.最长上升子序列;

import java.util.Scanner;

/**
 * @author ty
 * @create 2023-03-31 11:29
 */
public class 拦截导弹 {
    static int N =  1010;
    static int n;
    static int[] q = new int[N];
    static int[] f = new int[N];
    static int[] g = new int[N];
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String[] line = sc.nextLine().split(" ");
        n = line.length;
        for (int i = 0;i < n;i++) {
            q[i] = Integer.parseInt(line[i]);
        }
        //先求最长下降子序列
        int res = 0;
        for (int i = n-1;i >= 0;i--) {
            f[i] = 1;
            for (int j = n-1;j > i;j--) {
                //题目要求是不高于上一个炮弹,所以此处下降序列可以连续相等
                if (q[i] >= q[j]) {
                    f[i] = Math.max(f[i],f[j] + 1);
                }
            }
            res = Math.max(res,f[i]);
        }
        System.out.println(res);
        //g数组存现有的子序列
        //cnt表示当前子序列个数
        int cnt = 0;
        for (int i = 0;i < n;i++) {
            int k = 0;
            //找到第一个最长下降子序列最后一个值,大于等于q[i]的
            while (k < cnt && g[k] < q[i]) k++;
            g[k] = q[i];
            //没有序列,能存储当前元素
            if (k >= cnt) {
                cnt++;
            }
        }
        System.out.println(cnt);
    }
}

187. 导弹防御系统

在这里插入图片描述

比1010变化:拦截系统新增可以单调上升的选择

即最少可以用多少个上升子序列和下降子序列将整个序列覆盖掉

此时不能用贪心,因为最开始还要面临选择上升还是下降,这是没法做到的,只能使用暴搜

在这里插入图片描述

package acwing_plus.动规.最长上升子序列;

import java.util.Scanner;

/**
 * @author ty
 * @create 2023-03-31 12:45
 */
public class 导弹防御系统 {
    static int N = 55;
    static int[] q = new int[N];
    static int[] up = new int[N];
    static int[] down = new int[N];
    static int n;
    //记录全局最小值
    static int ans;

    /**
     * @param u 当前枚举到第几个数
     * @param su 当前上升子序列的个数
     * @param sd 当前下降子序列的个数
     */
    private static void dfs(int u, int su, int sd) {
        if (su + sd  >= ans) return;//超出最小的ans
        //找到方案
        if (u == n) {
            //更新最小方案数
            ans = su + sd;
            return;
        }
        //情况一,将当前数放到上升子序列中
        int k = 0;
        //上升子序列是找到第一个小于它的数
        while (k < su && up[k] >= q[u]) {
            k++;
        }
        int t = up[k];
        up[k] = q[u];
        //说明没有开辟新的上升子序列
        if (k < su) {
            dfs(u+1,su,sd);
        }else dfs(u+1,su+1,sd);//开辟了新的上升子序列
        //恢复现场
        up[k] = t;

        //情况二:将当前数放到下降子序列中
        k = 0;
        while (k < sd && down[k] <= q[u]) k++;
        t = down[k];
        down[k] = q[u];
        //说明没有开辟新的下降子序列
        if (k < sd) dfs(u+1,su,sd);
        else dfs(u+1,su,sd+1);
        down[k] = t;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while ((n = sc.nextInt()) != 0){
            ans = n;
            for (int i = 0;i < n;i++) {
                q[i] = sc.nextInt();
            }
            dfs(0,0,0);
            System.out.println(ans);
        };
    }
}

272.最长公共上升子序列

最长公共子序列在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

划分方式

n^3 TLE
package acwing_plus.动规.最长上升子序列;

import java.util.Scanner;

/**
 * @author ty
 * @create 2023-03-31 14:22
 */
public class 最长公共上升子序列 {
    static int N = 3010;
    static int n;
    static int[] a = new int[N];
    static int[] b = new int[N];
    static int[][] f = new int[N][N];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        for (int i = 1; i <= n; i++) a[i] = sc.nextInt();
        for (int i = 1; i <= n; i++) b[i] = sc.nextInt();
        /*
        f[i][j]表示所有第一个序列中前i个字母和第二个序列中前j个字母构成的
            且以b[j]结尾的公共上升子序列的最长长度
        f[i][j]来自于
        情况一:不包含a[i]=>f[i-1][j]
        情况二:包含a[i],前提a[i]==b[j]
                ->再按照倒数第二个数的位置进行划分
                ->倒数第二个数的位置可以取,空值(即只有b[j]一个元素),b[1],b[2]...b[j-1]
                ->需要满足对于上述位置k,b[j]>b[k]
                =>f[i,j] = max(f[i,j],f[i,k]+1)
        最终答案是max(f[n,i]),i = 1...n
         */
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                f[i][j] = f[i - 1][j];
                //第二种
                if (a[i] == b[j]) {
                    //空集
                    f[i][j] = Math.max(f[i][j], 1);
                    for (int k = 1; k < j; k++) {
                        if (b[j] > b[k]) {
                            f[i][j] = Math.max(f[i][k] + 1, f[i][j]);
                        }
                    }
                }
            }
        }
        int res = 0;
        for (int i = 1; i <= n; i++) {
            res = Math.max(res, f[n][i]);
        }
        System.out.println(res);
    }
}
优化
import java.util.Scanner;

/**
 * @author ty
 * @create 2023-03-31 14:22
 */
public class Main {
    static int N = 3010;
    static int n;
    static int[] a = new int[N];
    static int[] b = new int[N];
    static int[][] f = new int[N][N];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        for (int i = 1; i <= n; i++) a[i] = sc.nextInt();
        for (int i = 1; i <= n; i++) b[i] = sc.nextInt();
        /*
        f[i][j]表示所有第一个序列中前i个字母和第二个序列中前j个字母构成的
            且以b[j]结尾的公共上升子序列的最长长度
        f[i][j]来自于
        情况一:不包含a[i]=>f[i-1][j]
        情况二:包含a[i],前提a[i]==b[j]
                ->再按照倒数第二个数的位置进行划分
                ->倒数第二个数的位置可以取,空值(即只有b[j]一个元素),b[1],b[2]...b[j-1]
                ->需要满足对于上述位置k,b[j]>b[k]
                =>f[i,j] = max(f[i,j],f[i,k]+1)
        最终答案是max(f[n,i]),i = 1...n
         */
        for (int i = 1; i <= n; i++) {
            int maxv = 1;//表示a[i]==b[j],1到j-1在满足a[i]>b[k]条件下的最大值
            for (int j = 1; j <= n; j++) {
                f[i][j] = f[i - 1][j];
                //第二种
                if (a[i] == b[j]) {
                    f[i][j] = Math.max(f[i][j], maxv);
                }
                /*
                这里的理解是,因为本质上之前更新的时候是b[k] < b[j]
                转换为b[k]<a[i]是因为b[j]==a[j],即选择到a[i]
                但是此时更新时,a[i]是我们将要寻找的b[j],可以理解为在执行到
                a[i]==b[j]之前的更新,都是没有选用a[i]的情况下的
                 */
                if (b[j] < a[i]) maxv = Math.max(maxv, f[i-1][j] + 1);

            }
        }
        int res = 0;
        for (int i = 1; i <= n; i++) {
            res = Math.max(res, f[n][i]);
        }
        System.out.println(res);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值