第三期:动态规划(DP) 🔥 🔥 🔥
蓝桥杯热门考点模板总结来啦✨ ~
你绝绝绝绝绝绝对不能错过的常考DP模板 💥
❗️ ❗️ ❗️
祝大家4月8号蓝桥杯上岸 ☀️ ~
还没背熟模板的伙伴们背起来 💪 💪 💪
祝大家4月8号蓝桥杯上岸 ☀️ ~
不清楚蓝桥杯考什么的点点下方👇
考点秘籍
想背纯享模版的伙伴们点点下方👇
蓝桥杯省一你一定不能错过的模板大全(第一期)
蓝桥杯省一你一定不能错过的模板大全(第二期)
想背注释模版的伙伴们点点下方👇
蓝桥杯必背第一期
蓝桥杯必背第二期
往期精彩回顾
蓝桥杯上岸每日N题 第一期(一)!!!
蓝桥杯上岸每日N题第一期(二)!!!
蓝桥杯上岸每日N题第一期(三)!!!
蓝桥杯上岸每日N题第二期(一)!!!
蓝桥杯上岸每日N题第三期(一)!!!
操作系统期末题库 第九期(完结)
LeetCode Hot100 刷题(第三期)
idea创建SpringBoot项目报错解决方案
数据库SQL语句(期末冲刺)
想看JavaB组填空题的伙伴们点点下方 👇
填空题
竞赛干货
算法竞赛字符串常用操作大全
蓝桥杯上岸必刷!!!(模拟/枚举专题)
前言
蓝桥杯早已不是前几届的暴力杯,虽然还能靠暴力得一部分分,但是大趋势是越来越注重算法和数据结构的应用,这几年大佬们常说蓝桥杯是"DP杯",可见DP的重要,也确实如此,DP的占比越来越高,像李白打酒加强版等等题目。因此,我们需要学会DP。考前冲刺最高性价比的就是背模板!
下面让我们开始复习DP,冲刺最后的"DP杯"。
模板千千万万遍,其益自然现 👊 。
冲冲冲 ❗️ ❗️ ❗️
DP模板
下标一般从1开始,f[i-1]
防止数组下标越界!
背包区别
0-1背包:i 件物品中,每件物品最多选 1 次
完全背包:i 件物品中,每件物品可以选无限次
多重背包:枚举第 i 件物品选几个
分组背包:枚举第 i 组物品选第几个(哪个)
DP总结:
DP需要划分好集合:
首先最关键的是集合的定义!!!
你定义了什么,从该定义出发。
再是集合的划分
分为几类去处理,
一类是选/不选
一类是类中还细分各种类
属性的确定:
max
、min
、count
前两个**max
、min
是可以允许重合**的
最后的答案从定义出发,输出即可。
DP基操:
1.找不同和相同之处/不同和固定之处
2.先把固定/相同的剔除,最后再把他加上
3.从剩下的范围中进行选择方案
4.结合定义出发,输出需要的答案!
0-1背包 (经典模型)❗️
权值/重量的背包问题
求最大权值/最大重量
注意:
去掉第**i
**个物品后,需要回过头来加上该物品的权值/重量
import java.util.*;
public class Main{
static int N=1010;
static int f[][]=new int[N][N];
static int w[]=new int[N];
static int v[]=new int[N];
public static void main(String []args){
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int m=sc.nextInt();
for(int i=1;i<=n;i++){
v[i]=sc.nextInt();
w[i]=sc.nextInt();
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
//不选第i个物品,从前i-1个物品中选,体积为j
f[i][j]=f[i-1][j];
//选第i个物品,从前i-1个物品中选,体积为j-v[i]
if(j>=v[i])f[i][j]=Math.max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
}
//表示从n件物品中体积最大为m的最大价值的集合。
//输出最大价值
System.out.println(f[n][m]);
}
}
完全背包 (经典模型) ❗️
与0-1背包的不同:
0-1背包:每个物品只可以选择1次
完全背包:每个物品可以选择无限次
import java.util.*;
public class Main{
static int N=1010;
static int f[][]=new int[N][N];
static int v[]=new int[N];
static int w[]=new int[N];
public static void main(String []args){
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int m=sc.nextInt();
for(int i=1;i<=n;i++){
v[i]=sc.nextInt();
w[i]=sc.nextInt();
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
f[i][j]=f[i-1][j];
//第i个物品不能选择
//从前i-1个物品中可选无限次
//第i个物品可以选择
//从前i个物品中可选无限次
if(j>=v[i])f[i][j]=Math.max(f[i][j],f[i][j-v[i]]+w[i]);
}
}
//输出n个物品体积最大为m的最大价值
System.out.println(f[n][m]);
}
}
多重背包
背包问题下标基本从1开始,状态方程中的体积除外。
状态表示:
集合:所有只从前i
个物品中选,并且总体积不超过j
的选法
属性:max
状态计算:
**类似于完全背包,不过多了件数的限制。不过这里是从n
个物品的s
件中选择。可以从该物品中选0
件,1
件,2
件,3
件,…,s[i]
件,最多取s[i]
件。选了该物品的k
件后,剩下的再从i-1
**个物品中继续选取。
得到状态转移方程如下:
f[i][j]=f[i-1][j-v[i]*k]+w[i]*k
时间复杂度:
总共是3重循环,2维f[i][j]。
时间复杂度最坏为**O(n^3)**
平均时间复杂度为O(n^2logn)
注意
求的是所有方案的集合,答案一定要从定义出发!
答案恰好是**n
个物品中选择m
件的最大价值**
Accode
import java.util.*;
public class Main{
static int N=1010;
static int v[]=new int[N];
static int w[]=new int[N];
static int s[]=new int[N];
static int f[][]=new int[N][N];
public static void main(String []args){
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int m=sc.nextInt();
for(int i=1;i<=n;i++){
v[i]=sc.nextInt();
w[i]=sc.nextInt();
s[i]=sc.nextInt();
}
for(int i=1;i<=n;i++){
//从1到n件物品中选择
for(int j=0;j<=m;j++){
//背包体积为0到m
for(int k=0;k<=s[i]&&k*v[i]<=j;k++){
//选取物品的件数为k件
//1个物品可选取的件数最多为s[i]件
//k件选取的体积最多为当前的j
if(j>=v[i]*k)f[i][j]=Math.max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);
//可以从该物品中选0件,1件,2件,3件,...,s[i]件。
//最多可以取s[i]件。
//选了该物品的k件后,剩下的再从i-1个物品中继续选取。
}
}
}
System.out.println(f[n][m]);
}
}
分组背包 :
状态表示:
集合:从前**i
组物品中选,且总体积不大于**j
的所有选法
属性:max
状态计算:
第i组物品中一个都不选:
那只能是从前**i-1
组物品中选且体积最大为j
**
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
]
f[i][j]=f[i-1][j]
f[i][j]=f[i−1][j]
第i组物品中选第k个物品:
每组最多只能选择1个物品,第**i
组选完后,再从前i-1
组中选择,体积j
减去已经选择的v[i,k]
,价值需要加上第i
组第k
件物品的价值****w[i,k]
**。
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
−
v
[
i
]
[
k
]
]
+
w
[
i
]
[
k
]
f[i][j]=f[i-1][j-v[i][k]]+w[i][k]
f[i][j]=f[i−1][j−v[i][k]]+w[i][k]
二维
import java.util.*;
public class Main{
static int N=110;
static int s[]=new int[N];
static int v[][]=new int[N][N];
static int w[][]=new int[N][N];
static int f[][]=new int[N][N];
public static void main(String []args){
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int m=sc.nextInt();
for(int i=1;i<=n;i++){
s[i]=sc.nextInt();
for(int j=1;j<=s[i];j++){
v[i][j]=sc.nextInt();
w[i][j]=sc.nextInt();
}
}
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
f[i][j]=f[i-1][j];
for(int k=1;k<=s[i];k++){
//第i组选了第k件物品
//当j>=v[i][k]时才执行下列语句
if(j>=v[i][k])f[i][j]=Math.max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);
}
}
}
System.out.println(f[n][m]);
}
}
一维优化版
import java.util.*;
public class Main{
static int N=110;
static int s[]=new int[N];
static int v[][]=new int[N][N];
static int w[][]=new int[N][N];
static int f[]=new int[N];
public static void main(String []args){
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int m=sc.nextInt();
for(int i=1;i<=n;i++){
s[i]=sc.nextInt();
//共有s[i]组
for(int j=0;j<s[i];j++){
v[i][j]=sc.nextInt();
//第i组第j件物品的体积
w[i][j]=sc.nextInt();
//第i组第j件物品的重量
}
}
for(int i=1;i<=n;i++){
//n组物品
for(int j=m;j>=0;j--){
//注意这里的体积从m开始枚举
for(int k=0;k<s[i];k++){
//第i组选了第k件物品
//当j>=v[i][k]时才执行下列语句
if(j>=v[i][k])f[j]=Math.max(f[j],f[j-v[i][k]]+w[i][k]);
}
}
}
//输出最大价值
System.out.println(f[m]);
}
}
线性DP
数字三角形 (经典模型)❗️
输入是读取的每一行(横向)、每一列(纵向)
理解:纵向的列看成是斜向下走的,便于处理。
状态表示:
集合:所有从起点走到**(i,j)
**这个点的路径
属性:max
状态计算:
这里我们计算的是从起点走到**(i,j)
这个点的路径数字之和的最大值**。我们观察可以发现是从左上和右上方两条路径可以走到**(i,j)
**这个点。**两条路径同时加上一个数/减去一个数,路径的和值是不变的。我们可以先把(i,j)
这个点去掉,转化为去求到左上方和右下方点的路径的数字之和,最后再将a[i,j]
**这个点的数字加上即可。
从左上方走到**(i,j)
**这个点的路径数字之和:
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
−
1
]
+
a
[
i
]
[
j
]
f[i][j]=f[i-1][j-1]+a[i][j]
f[i][j]=f[i−1][j−1]+a[i][j]
从右上方走到**(i,j)
这个点的路径数字之和**:
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
]
+
a
[
i
]
[
j
]
f[i][j]=f[i-1][j]+a[i][j]
f[i][j]=f[i−1][j]+a[i][j]
再将两种情况取一个Max即可
import java.util.*;
public class Main{
static int INF=0x3f3f3f3f;
static int N=510;
static int f[][]=new int[N][N];
static int a[][]=new int[N][N];
public static void main(String []args){
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
a[i][j]=sc.nextInt();
}
}
for(int i=0;i<=n;i++){
for(int j=0;j<=i+1;j++){
f[i][j]=-INF;
//先把每个点的距离初始化为-INF
//一般图论问题都是先将点的距离初始化为-INF
}
}
//第1层已初始化
//遍历第2层到第n层的三角形的所有点
//直至最后一层遍历完毕
f[1][1]=a[1][1];
//起点为(1,1),只有一个点,f[1][1]=a[1][1]。
for(int i=2;i<=n;i++){
//从第二行开始遍历
for(int j=1;j<=i;j++){
//到达当前点的路径为左上和右上两条路径走过来
//再把当前点的数字加上即可
f[i][j]=Math.max(f[i-1][j-1]+a[i][j],f[i-1][j]+a[i][j]);
}
}
//走到最后一行的每一列即最后一行的每个点
//先将答案初始化为f[n][1]
int res=f[n][1];
//res定义为f[n][1](最稳妥)
//不要定义成0,存在负数的情况
//定义成-INF也可(看数据范围)
for(int i=1;i<=n;i++){
//遍历最后一行的每个点
//取出max
res=Math.max(f[n][i],res);
}
System.out.println(res);
}
}
最长上升子序列 ❗️
状态表示:
集合:所以**a[i]
**为结尾的严格单调递增的子序列
属性:max
注:子序列的结尾必定是以某个数**a[i]
作为结尾,我们求出所有数作为结尾的子序列**,然后取一个max即可。
划分依据:最后一个不同的点
所有以**a[i]
为结尾的子序列,说明a[i]
**均相同,我们根据最后一个不同的点划分。
状态计算:
根据最后一个不同的点,可以将最后一个不同的点以**a[1]
、a[2]
、a[3]
、…a[i-1]
这几类进行划分,由于这几类划分标准的共同点均是以a[i]
结尾,所以最后再加上a[i]这个点即可,即长度加1**。
得到状态转移方程如下:
f [ i ] = M a t h . m a x ( f [ i ] , f [ j ] + 1 ) f[i]=Math.max(f[i],f[j]+1) f[i]=Math.max(f[i],f[j]+1)
特殊情况:
只有**a[i]
自己一个数的时候,此时长度为1
。这里需要在循环时进行特判,如果前面找不到比a[i]
**小的数,即a[j]>=a[i]
时,则不需要更新长度。
为什么需要特判?
a[j]>a[i]:
我们是以a[i]
作为结尾的子序列,子序列严格递增,a[i]
是最大的那个数。前面出现比a[i]
****大的数就不满足这一条件,不需更新长度。
a[j]=a[i]:
当出现a[j]=a[i]
时说明这两个数相同,我们只需要保留**a[i]
这个数即可。即长度为1**.
Accode
import java.util.*;
public class Main{
static int N=1010;
static int a[]=new int[N];
static int f[]=new int[N];
public static void main(String []args){
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
for(int i=1;i<=n;i++)a[i]=sc.nextInt();
for(int i=1;i<=n;i++){
f[i]=1;//初始化每个i结尾的序列长度为1
for(int j=1;j<i;j++){
if(a[j]<a[i]){
//特判一下
//a[j]<a[i]需要更新
//a[j]>=a[i]说明序列不合法
f[i]=Math.max(f[i],f[j]+1);
}
}
}
long res=f[1];
for(int i=1;i<=n;i++){
res=Math.max(res,f[i]);
}
System.out.println(res);
}
}
最长上升子序列II
优化:处理冗余的数据
我们把数字接到结尾大的数字前面一定可以把该数字接到结尾更小的数字后面。
保留结尾小的数字可以接收更多的数字,所以保留结尾小的数字一定比保留结尾大的数字要好。
随着序列长度的递增,结尾的数字一定是单调递增的。
为什么?
假设长度为5的序列,结尾为**a
。长度为6的序列,结尾为b
**。
假设结尾**b
比结尾a
要小**,那结尾**b
应该是在长度为5的序列的结尾。**
不同长度上升子序列结尾保留的是后面最小的值,长度为5的序列的结尾取到的是后面数的最小值**a
**。
a
是当前长度上升子序列结尾的最小值,所以就不可能取到**b
**。
所以,长度为6的序列的结尾必定是大于长度为5的序列。
所以,a<b
。因此,随着序列长度的递增,结尾数值的大小也随之递增。
二分:
二分出来小于某个数(结尾)的最大的一个数,再把结尾往后一个位置**(r+1)
**放。
时间复杂度:O(nlogn)
一共有**n
个数要二分**,每次二分的时间复杂度为**O(logn)**
所以总共的时间复杂度为**O(nlogn)**
Accode
import java.util.*;
public class Main{
static int N=100010;
static int a[]=new int[N];
static int q[]=new int[N];
public static void main(String []args){
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
for(int i=0;i<n;i++)a[i]=sc.nextInt();
int len=0;
//从长度0开始枚举
for(int i=0;i<n;i++){
int l=0,r=len;
while(l<r){
//二分
int mid=l+r+1>>1;
//l=mid,注意要加1
//二分出小于某个数的最大的一个数
//再把结尾放到后一个位置
if(q[mid]<a[i])l=mid;
else r=mid-1;
}
//更新长度
len=Math.max(len,r+1);
//二分出的位置放的是比结尾小的最大值
//将结尾数插入到二分出的下一个位置
//q数组存的是每种长度的结尾值
q[r+1]=a[i];
}
System.out.println(len);
}
}
最长公共子序列
状态表示:
**集合:所有从A[1,i] B[1,j]
**的公共子序列的集合
属性:max
像最长上升子序列,状态的划分依据是找不同的点。最长上升子序列确定以**a[i]
为结尾的子序列,共同点便是以a[i]
作为结尾。划分依据:a[1,i-1]
中的每个数作为最后一个不同点进行划分**
最长公共子序列的不同点在于**i,j
在不在序列中,可以将集合划分为4类。
实际上只有3类,且往下看。
(1)i、j
均不在**
i、j
均不包含其中,那只能从a[i-1]、b[j-1]
中去选
得到如下状态转移方程:
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
−
1
]
f[i][j]=f[i-1][j-1]
f[i][j]=f[i−1][j−1]
(2)i
不在,j
在
i
不在序列中,只能从前i-1
中选,j
可在可不在。
刚好**i
不在,j
在,包含在这种情况中,且最大值/最小值是可以允许重复的。
得到如下状态转移方程:
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
]
f[i][j]=f[i-1][j]
f[i][j]=f[i−1][j]
(3)i
在,j
不在**
**j
不在序列中,那j
只能从j-1
**中选择。i
可在可不在。
刚好**i
在,j
**不在,包含在这种情况中,且最大值/最小值是可以允许重复的。
得到如下状态转移方程:
f
[
i
]
[
j
]
=
f
[
i
]
[
j
−
1
]
f[i][j]=f[i][j-1]
f[i][j]=f[i][j−1]
(4)i、j
均在
只有满足**a[i]==b[j]
这一条件才存在**。
确定了**a[i]、b[j]
之后,剩下的从i-1、j-1
中选出最长公共子序列,从定义出发,恰好就是f[i-1][j-1]
中选。最后再加上固定好的a[i]、b[j]
这一对即可。
得到如下状态转移方程:
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
−
1
]
+
1
f[i][j]=f[i-1][j-1]+1
f[i][j]=f[i−1][j−1]+1
最后,(1)是既可以包含在(2)也可以包含在(3)中的,允许最值重复**。重复没关系,求出的必定是最值且包含在整个集合中,是合法的。最后,总共只有3种情况。
求和/求值则不允许重复!!!
题解
Accode
import java.util.*;
public class Main{
static int N=1010;
static int f[][]=new int[N][N];
public static void main(String []args){
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int m=sc.nextInt();
char a[]=(" "+sc.next()).toCharArray();
char b[]=(" "+sc.next()).toCharArray();
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
//情况1包含在情况2、3中
//情况2、3取一个max
f[i][j]=Math.max(f[i-1][j],f[i][j-1]);
//满足a[i]==b[j]这一条件
//再与情况4取一个max
if(a[i]==b[j])f[i][j]=Math.max(f[i][j],f[i-1][j-1]+1);
}
}
//输出集合定义
System.out.println(f[n][m]);
}
}
区间DP
石子合并 (经典模形)❗️
n堆石子合并:
最开始的选择为n-1
堆
合并后的选择为n-2
堆
依次类推:合并的选择为(n-1)!
n=300-->299!
(必定会TLE)
由于是合并相邻两堆:可以采用DP的方式来做
如果合并的不是相邻两堆:可以用贪心的方式来做
合并石子:采用分治的策略
先把左边合并成一堆,再把右边合并成一堆。
最后再加上石子总和即可。
由于两堆待合并的石子是相互独立,互不干涉的。
要使合并的总代价最小
要确保左边合并的代价最小,也要确保右边合并的代价最小。
思路
先枚举区间长度
区间长度为1时,一堆石子不用合并。
for(int len=2;len<=n;len++)
再枚举左端点和右端点
左端点:
for(int i=1;i+len-1;i++)
右端点:
int j=i+len-1;
最后枚举移动的分界点 k
for(int k=1;k<j;k++)
以k作为分割点,将该枚举的区间分成两堆石子
**这里的 k 不能等于 j **
合并两堆石子,相当于 j 是另一堆。
ACcode
import java.util.*;
public class Main{
static int N=310;
static int s[]=new int[N];
static int f[][]=new int[N][N];
static int INF=0x3f3f3f3f;
public static void main(String []args){
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
for(int i=1;i<=n;i++){
s[i]=sc.nextInt();
s[i]+=s[i-1];
}
//长度为1时,石子只有一堆不用合并
for(int len=2;len<=n;len++){
//先枚举区间长度
for(int i=1;i+len-1<=n;i++){
//再枚举左端点和右端点
int j=i+len-1;//右端点
f[i][j]=INF;//初始化化INF,便于求最小值
for(int k=i;k<j;k++){
//以k作为分割点,将该枚举的区间分成两堆石子
//k不能等于j,等于j时只有一堆石子
//一堆石子是不用合并的
f[i][j]=Math.min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
}
}
}
//输出集合定义:1~n堆石子合并成1堆的答案
System.out.println(f[1][n]);
}
}
树形DP
没有上司的舞会(经典模形)❗️
图的存储:
邻接表
不选当前的根节点:
f[u][0]+=Math.max(f[j][0], f[j][1]);
不选当前的根节点,就要从不选子树节点和选子树节点取一个max
选择当前的根节点
f[u][1]+=f[j][0];
选择当前的根节点,就不能选子树节点,加上不选子树节点的取法即可。
Accode
import java.util.*;
public class Main{
static int N=6010;
static int []happy=new int[N];
static int []h=new int[N];
static int []e=new int[N];
static int []ne=new int[N];
static int idx=0;
static int [][]f=new int[N][2];
static boolean []has_father = new boolean [N];
public static void add(int a,int b) {
//邻接表存边
e[idx]=b;
ne[idx]=h[a];
h[a]=idx;
idx++;
}
public static void dfs(int u) {
f[u][1]=happy[u];
//表示选了这个根节点,就要加上这个根节点的happy值
for(int i=h[u];i!=-1;i=ne[i]) {
//遍历所有邻接点
int j=e[i];
dfs(j);
//遍历当前点j的所有子树
f[u][0]+=Math.max(f[j][0], f[j][1]);
//不选当前的根节点,就要从不选子树节点和选子树节点取一个max
f[u][1]+=f[j][0];
//选择当前的根节点,就不能选子树节点,加上不选子树节点即可。
}
}
public static void main(String []args)
{
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
for(int i=1;i<=n;i++)happy[i]=sc.nextInt();
Arrays.fill(h, -1);
for(int i=0;i<n-1;i++) {
int a=sc.nextInt();
int b=sc.nextInt();
add(b,a);
has_father[a]=true;
//表示当前节点有父亲
}
int root=1;
while(has_father[root])root++;
//找到根节点
dfs(root);
//dfs根节点的所有子树
System.out.println(Math.max(f[root][0], f[root][1]));
//两种方案,选或不选当前根节点取一个max
}
}
整数划分
类似于完全背包问题 :每个数可以无限次选择
由于每个数字可以无限次选择
可以理解为从**1到i-1
件物品中选择k
件第i
件物品
不选第i
件物品即f[i-1][j]
**
选1件第**i
件物品即f[i-1][j-i]
**
选2件第**i
件物品即f[i-1][j-2*i]
**
选3件第**i
件物品即f[i-1][j-3*i]
**
选k件第**i
件物品即f[i-1][j-k*i]
**
即f**[i][j]=f[i-1][j]+f[i-1][j-2*i]+...+f[i-1][j-k*i]
**
公式变换
类似于完全背包:j
那一项全部减i
f[i][j]=f[i-1][j]+f[i-1][j-i]+f[i-1][j-2*i]+...+f[i-1][j-k*i]
f[i][j-i]= f[i-1][j-i]+f[i-1][j-2*i]+...+f[i-1][j-k*i]
进行替换得到:f[i][j]=f[i-1][j]+f[i][j-i]
二维优化成一维:
二维:f[i][j]=f[i-1][j]+f[i][j-i];
一维:f[j]=f[j]+f[j-i];
Accode
//二维:f[i][j]=f[i-1][j]+f[i][j-i];
//一维:f[j]=f[j]+f[j-i];
import java.util.*;
public class Main{
static int N=1010;
static int mod=(int)(1e9+7);
static int f[]=new int[N];
public static void main(String args[]){
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
f[0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(j>=i)f[j]=(f[j]+f[j-i])%mod;
}
}
System.out.println(f[n]);
}
}
✨ ✨ ✨
看到这里,不妨点个关注 💖