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外都为-1long[][] 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整除,那么就输出1System.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;}
}