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;
}
}