一、线性dp
问题:给定一个正整数组成的数组,取出一些数使得和尽可能大,要求不能取相邻的数。len 1e6
例如:3,2,6,9,1 取3+9=12
例如:2,4,6,8,9 取2+6+9=17
dp[i][0]取了第i个数的情况下,前i个数取完的最大值
dp[i][1]没取第i个数的情况下,前i个数取完的最大值
得到递推关系:
- dp[i][0]=dp[i-1][1] +a[i] //第i个数取了,必定要第i-1个数不取。
- dp[i][1]=max(dp[i-1][0],dp[i-1][i]) //第i个数不取,第i-1个数可取可不取。
二、前缀和、差分
问题1:给定一个数组,多次询问[l,r]区间的总和是多少?要求做到O(1)询问
首先求出前缀和数组dp[i] (前i个数的和);使用后缀和也一样。
for(i=1;i<=n;i++) dp[i]=dp[i-1]+a[i];
dp[r]-dp[l-1] //区间的总合
问题2:给定一个数组,多次操作,每次操作[l,r]区间每个数加x,操作结束后输出数组 q次操作
假如有一个数组 1 2 3 4 5 6 7 8 需要在[2,4]上加1 ==》1 3 4 5 5 6 7 8
1.首先给出一个全为0的数组dp[i] 0 0 0 0 0 0 0 0 ;
2.然后使dp[l]+=x;dp[r+1]-=x ==》 0 1 0 0 -1 0 0 0
3.前缀和后 0 1 1 1 0 0 0。
2和3步骤之间可以操作q次。(这一系列操作就是差分)
三、二维dp
问题:给定一个矩阵,初始在左上角,每一步可以向右或者向下,问路径数字和的最大值。
dp[i][j]代表从左上角到(i,j) 路径最大值
dp[i][j]=max(dp[i-1][j],dp[i][j-1])+a[i][j]
1
2
3
5
2
4
2
4
8
1
4
7
8
4
11
2
5
7
9
3
四、背包
问题1:n个物品,每个物品有重量a[i]和价值b[i],背包限制总重量w,问放入背包的物品价值最大值多少?要求复杂度O(n*w) 01背包
设dp[i][j] 代表前i个物品中,取总重量为j的物品的价值最大值。
那么有方程:dp[i][j]=max(dp[i][j],dp[i-1][j-a[i]]+b[i])
解释如下:如果取第i个物品,那么“前i个物品中,取总重量为j的物品”这个状态一定是从“前i-1个物品中,取总重量为j-a[i]的物品”这个状态再取第i个物品达成的。
问题2:n个物品,每个物品重量a[i]。问能否取部分物品达成总重量正好为w?要求复杂度O(n*w)
这道题如果n的范围是20,那么可以直接用状压枚举或dfs来解决。但是如果n比较大呢?比如n是100,w是1000的情况。
我们不妨设dp[i]代表重量i能被选到。那么由前i-1个物品能选到的所有情况显然可以直接推出第i个物品的。
五、树形dp
给定一棵有根树,求每个节点的子树大小。 一次dfs + dp 就可以出来了
int dp[100010]; int dfs(int x){ int i,sum=a[x]; for(i=0;i<g[x].size();i++){ if(g[x][i]!=pr) sum+=dfs(g[x][i]); } dp[x]=sum; return sum; }
六、算法题
1.不相邻取数 难度⭐⭐
小红拿到了一个数组。她想取一些不相邻的数,使得取出来的数之和尽可能大。你能帮帮她吗?(具体思路上面写的有)
示例1:
输入
4 2 6 4 1输出
7说明
取 6 和 1 即可
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
String[] arr = br.readLine().split(" ");
int[] nums = new int[n];
for (int i = 0; i < n; i++) {
nums[i] = Integer.parseInt(arr[i]);
}
System.out.println(test(nums, n));
}
//打家劫舍
public static int test(int[] nums, int n){
if(n == 1){
return nums[0];
}else if(n == 2){
return Math.max(nums[0], nums[1]);
}
//nums[1] 要init nums[3]时要用到
nums[1] = Math.max(nums[0], nums[1]);
for (int i = 2; i < n; i++) {
nums[i] = Math.max(nums[i - 2] + nums[i], nums[i - 1]);
}
return nums[--n];
}
}
这里的方法同样是开头的思想,只不过没有使用二维数组来区分取或者没取。
nums[i] = Math.max(nums[i - 2] + nums[i], nums[i - 1]);
nums[i]表示前i个数满足条件的最大和。nums[i - 2] + nums[i]代表取了;nums[i - 1]代表没取 (为什么会这么设计呢?因为我们保存的值只需要一个,即最大值。而二维数组保存了两种情况下对应的两个值,那我们为何不将两种值比较然后赋值呢?)
2.abb型子序列 难度⭐⭐⭐
知识点:后缀和
枚举每个字符a[i],对于这个字符而言,需要求后面取两个相同字符(和当前字符不等)的方案数。 预处理出每种字母的后缀和,对于当前字母str,遍历25种和str不同的字母对应的后缀和,每个贡献为
。
leafee 最近爱上了 abb 型语句,比如“叠词词”、“恶心心”
leafee 拿到了一个只含有小写字母的字符串,她想知道有多少个 "abb" 型的子序列?
定义: abb 型字符串满足以下条件:
- 字符串长度为 3 。
- 字符串后两位相同。
- 字符串前两位不同。
输入描述:
第一行一个正整数 n
第二行一个长度为 n 的字符串(只包含小写字母)
1<=n<=10^5
输出描述:
"abb" 型的子序列个数。
示例1:
输入
6 abcbcc输出
8说明
共有1个abb,3个acc,4个bcc
示例2
输入
4 abbb输出
3
import java.io.*;
public class Main {
public static void main(String[] args)throws IOException {
long result = 0;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
long n = Integer.parseInt(br.readLine());
if (n < 3) {
System.out.println(0);
return;
}
char[] chars = br.readLine().toCharArray();
//a-z字母出现的次数
long[] sum = new long[26];
//当前对应字母的前面多少个与此字母不同
long[] dp = new long[26];
for (int i = 0; i < n; i++) {
//字母表的位置下标
int index = chars[i] - 'a';
//纪录了有多少个加上次字母后,可以形成abb的形式
result += dp[index];
//更新操作:加上前面的其他(非自身)的个数,来组成ab的形式
dp[index] += i - sum[index];
//对应个数+1
sum[index]++;
}
System.out.println(result);
}
}
这里使用的是动态规划的优化版
3.小红取数 难度⭐⭐⭐
知识点:dp
遇到这种题的思路: 首先第一个想法是,开一个dp数组,dp[i]代表取前i个数的最大值。 那么dp[i]怎么求呢?很明显,如果要取了第i个数a[i],满足和能被k整除,那么就有个限制:前i-1个数里,满足取的数之和除以k的余数为k-a[i]%k,之后加上a[i]才能正好被k整除。 所以一维数组是不够的,需要开二维数组:dp[i][j]代表前i个数里面,取一些数,相加的和除以k的余数为j的最大值。
这样就得到转移方程: dp[i][(j+a[i])%k]=max(dp[i-1][j]+a[i],dp[i-1][(j+a[i])%k]);
题目描述:
小红拿到了一个数组,她想取一些数使得取的数之和尽可能大,但要求这个和必须是 k 的倍数。
你能帮帮她吗?
示例1:
输入
5 5 4 8 2 9 1输出
20说明
取后四个数即可
import java.io.*;
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner in = new Scanner(System.in);
int n=in.nextInt();
int k=in.nextInt(),i,j;
long[] a = new long[n+1];
long[][] dp = new long[n+1][k+1];
for(i=1;i<=n;i++)a[i]=in.nextLong();
for(i=0;i<=n;i++){
for(j=0;j<k;j++){
dp[i][j]=-9999999999999999L;
}
}
dp[0][0]=0;
for(i=1;i<=n;i++){
for(j=0;j<k;j++){
//把余数为0到k-1的都选择性的加a[i]。
dp[i][(int)((j+a[i])%k)]=Math.max(dp[i-1][j]+a[i],dp[i-1][(int)((j+a[i])%k)]);
}
}
if(dp[n][0]<=0)System.out.println(-1);
else System.out.println(dp[n][0]);
}
}
4.宵暗的妖怪 难度⭐⭐⭐
知识点:dp
由于取区间只取中间的点,要求区间不能重叠,所以相当于限制取的所有点中,任意两点的距离不小于3。 所以可以效仿上次讲的例题,设dp[i][0]代表取第i个点,前i个数的最大和;dp[i][1]代表不取第i个点,前i个数的最大和(要注意第一个数和最后一个数永远不能取到)。
因此有dp转移方程: dp[i][0]=dp[i-2][0]+a[i]; dp[i][1]=max(dp[i-1][0],dp[i-1][1]);
露米娅作为宵暗的妖怪,非常喜欢吞噬黑暗。
这天,她来到了一条路上,准备吞噬这条路上的黑暗。
这条道路一共被分为 n 部分,每个部分上的黑暗数量为a_i。
露米娅每次可以任取 连续的 未被吞噬过的 三部分,将其中的黑暗全部吞噬,并获得中间部分的饱食度。
露米娅想知道,自己能获得的饱食度最大值是多少?
示例1:
输入
7 2 4 1 4 2 1 8输出
6说明
选择[2,4,1]和[4,2,1]这两段即可。饱食度为4+2=6。
示例2
输入
7 2 4 1 7 2 1 8输出
7说明
选择[1,7,2]这一段即可。饱食度为7。 值得注意的是,若取两段进行吞噬,反而最多只能获得6的饱食度,并不是最大的。
import java.io.*;
import java.util.*;
public class Main{
public static void main(String[] args)throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n=Integer.parseInt(br.readLine()),i;
//以防万一,都预留一点空间
int[] a = new int[n+1];
long[][] dp = new long[n+1][3];
String[] arr = br.readLine().split(" ");
for(i=0;i<n;i++)a[i]= Integer.parseInt(arr[i]);
dp[1][1]=a[1];
dp[2][1]=a[2];
dp[2][0]=a[1];
for(i=3;i<n-1;i++){
dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]);
dp[i][1]=Math.max(dp[i-3][0]+a[i],dp[i-3][1]+a[i]);
}
System.out.println(Math.max(dp[n-2][0],dp[n-2][1]));
}
}
5.小红的树 难度⭐⭐
知识点:树形dp、dfs/bfs
在搜索的过程中,统计所有儿子的子树红点数量,设dp[i]为第i个节点的子树红色节点的数量。
那么转移方程为: dp[x]=(所有儿子子树数量之和)+cnt 其中若当前点为红点,cnt为1,否则cnt为0。
题目描述:
小红拿到了一棵有根树。根节点为1号节点。
所谓树,指没有回路的无向连通图。
现在小红想给一部分点染成红色。之后她有 q 次询问,每次询问某点的子树红色节点的个数。
示例1:
输入
5 1 2 1 4 WRWRR 3 3 4 5输出
0 2 1说明
这棵树形状如上图。
可以发现,3号节点的子树没有红色节点。
4号节点的子树共有2个红色节点。
5号节点的子树共有1个红色节点。
import java.io.*;
import java.util.*;
public class Main{
static int[] dp;
static String str;
static ArrayList<ArrayList<Integer> >g;
public static void main(String[] args){
Scanner in = new Scanner(System.in);
int n=in.nextInt(),i;
dp=new int[n+5];
g=new ArrayList<>(n+10);
for(i=0;i<=n;i++)g.add(new ArrayList<>());
for(i=2;i<=n;i++){
int x=in.nextInt();
g.get(x).add(i);
}
str=in.next();
dfs(1,-1);
int q=in.nextInt();
while(q>0){
q--;
int x=in.nextInt();
System.out.println(dp[x]);
}
}
static int dfs(int x,int pr){
int i,sum=0;
if(str.charAt(x-1)=='R')sum++;
for(i=0;i<g.get(x).size();i++){
if(g.get(x).get(i)!=pr)sum+=dfs(g.get(x).get(i),x);
}
dp[x]=sum;
return sum;
}
}