未见过类型题每周总结(个人向)


1.DP40 小红取数

题目

解析 

一道01背包的衍生问题,我们可以按照它的思路定义数组dp[i][j],表示前i个数中%k为j的最大和。为什么设置未%k的最大和呢?是因为当两个数分别%k,如a%k=x,b%k=y。那么(a+b)%k==(x+y)%k。接下来推动态转移方程,取第i个数时dp[i][j]=dp[i-1][j-arr[i]%k]+arr[i],不取第i个数时dp[i][j]=dp[i-1][j],但是如果j-arr[i]%k<0,那么数组会越界,和普通的01背包不同,我们这里就算它小于0,也是存在意义的,就比如k==3时,arr[i]%3=2,j=1,这说明2加上了2再%3等于1,所以为了让数组不越界并且找到和arr[i]%k相加的那个数,我们把j-arr[i]%k变成(k+j-arr[i]%k)%k,初始化时i为0时除j=0外都为-1,最后输出dp[n][0]。还有一个特殊情况,如果没有任何数相加%k为0则j等于0就一直是0,但是我们要输出-1,所以最后我们要判断如果dp[n][0]为0,则输出-1。

代码

import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int k = in.nextInt();
        long[] arr = new long[n + 1];
        for (int i = 1; i <= n; i++) {
            arr[i] = in.nextLong();
        }
        //设dp[i][j]表示前i个数中%k为j的最大和
        //取第i个数时dp[i][j]=dp[i-1][j-arr[i]%k]+arr[i]
        //不取第i个数时dp[i][j]=dp[i-1][j]
        //如果j-arr[i]%k<0那就让它等于(k+j-arr[i]%k)%k
        //所以取第i个数时dp[i][j]=dp[i-1][(k+j-arr[i]%k)%k]+arr[i]
        //初始化i为0时除j=0外都为-1
        long[][] dp = new long[n + 1][k];
        for (int i = 1; i < k; i++) {
            dp[0][i] = -1;
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < k; j++) {
                if (dp[i - 1][(int)((k + j - arr[i] % k) % k)] != -1) {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][(int)((k + j - arr[i] % k) % k)] + arr[i]);
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        //如果到最后%k等于0的位置还为0,那么就说明从头到尾没有数相加能被
        //k整除,那么就输出1
        System.out.print(dp[n][0]==0?-1:dp[n][0]);
    }
}

2.DP16 合唱队形

题目

 解析

用最长上升子序列,以第i个为中心即可,d[i]表示从1到i的最大子序列,p[i]表示从n到i的最大子序列,d[i]=(d[0]到d[i-1]中小于这个数的最大值)+1,p[i]=(p[n]到p[i+1]中小于这个数的最大值)+1,每个数都要和1比,因为自身也有长度。

还有一种解法就是使用我在之前最长上升子序列(二)中运用的方法,贪心+二分查找。这个之前学过,可以去前面看。算是一种时间优化。

代码

解法一:
public class demo2 {//合唱队形
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int[] arr = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            arr[i] = in.nextInt();
        }
        //用最长上升子序列,以第i个为中心即可
        //d[i]表示从1到i的最大子序列
        //p[i]表示从n到i的最大子序列
        //d[i]=(d[0]到d[i-1]中小于这个数的最大值)+1
        //p[i]=(p[n]到p[i+1]中小于这个数的最大值)+1
        //初始化d[0],p[0]=0;
        //每个数都要和1比,因为自身也有长度
        int[] d=new int[n+1];
        int[] p=new int[n+1];
        for (int i = 1; i <= n; i++) {
            d[i] = 1;
            for (int j = 0; j < i; j++) {
                if (arr[j] < arr[i]) {
                    d[i] = Math.max(d[i], d[j] + 1);
                }
            }
        }
        for (int i = n; i >= 1; i--) {
            p[i] = 1;
            for (int j = n; j >i; j--) {
                if (arr[j] < arr[i]) {
                    p[i] = Math.max(p[i], p[j] + 1);
                }
            }
        }
        int min=0x3f3f3f3f;
        for(int i=1;i<=n;i++) {
            min=Math.min(min,n-(d[i]+p[i]-1));
        }
        System.out.print(min);
    }
}
 解法二:
/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 99715
 * Date: 2024-06-01
 * Time: 19:19
 */
import java.util.*;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class demo3 {//合唱队形(时间优化)
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int[] arr = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            arr[i] = in.nextInt();
        }
        //时间优化
        int[] d = new int[n + 1];
        int[] p = new int[n + 1];
        int[] d1 = new int[n + 1];
        int pos = 0;
        for (int i = 1; i <= n; i++) {
            if (pos == 0 || arr[i] > d1[pos]) {
                d1[++pos] = arr[i];
            } else {
                int left = 1, right = pos;
                while (left < right) {
                    int mid = (left + right) / 2;
                    if (arr[i] > d1[mid]) {
                        left = mid + 1;
                    } else {
                        right = mid;
                    }
                }
                d1[right] = arr[i];
            }
            d[i] = pos;
        }
        int pos2=0;
        int[] p1=new int[n+1];
        for (int i = n; i >= 1; i--) {
            if (pos2 == 0 || arr[i] > p1[pos2]) {
                p1[++pos2] = arr[i];
            } else {
                int left = 1, right = pos2;
                while (left < right) {
                    int mid = (left + right) / 2;
                    if (arr[i] > p1[mid]) {
                        left = mid + 1;
                    } else {
                        right = mid;
                    }
                }
                p1[right] = arr[i];
            }
            p[i] = pos2;
        }
        int min = 0x3f3f3f3f;
        for (int i = 1; i <= n; i++) {
            min = Math.min(min, n - (d[i] + p[i] - 1));
        }
        System.out.print(min);
    }
}

3.小红的子串

题目

解析

这一题的主要思想是滑动窗口,捎带着些前缀和。因为要判断种类在l到r之间的子串数,而这无法直接计算,所以我们使用1到r减去1到l-1来计算。那么我们怎么计算1到x种类之间的字串数呢?我们可以在每次进窗口的时候让ret加上这个数添加的子串个数。子串个数可以用r-l+1表示。如图:

代码

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 99715
 * Date: 2024-06-01
 * Time: 20:00
 */
import java.util.*;
public class demo4 {//小红的字串
    static char[] arr;
    static int n;
    public static void main(String[] args) {
        Scanner in=new Scanner(System.in);
        n=in.nextInt();
        int l=in.nextInt();
        int r=in.nextInt();
        arr=in.next().toCharArray();
        long ret=find(r)-find(l-1);
        System.out.print(ret);
    }
    static long find(int len) {
        int l=0,r=0,count=0;long ret=0;
        int[] hash=new int[26];
        while(r<n) {
            //进窗口
            if(hash[arr[r]-'a']++==0) count++;
            //判断合法以及使其合法
            while(count>len) {
                if(--hash[arr[l]-'a']==0) count--;
                l++;
            }
            ret+=r-l+1;
            r++;
        }
        return ret;
    }
}
  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值