蓝桥杯备赛Day12 动态规划1基础
动态规划
动态规划基础
动态规划将复杂问题分解成很多重叠的子问题,再通过子问题的解得到整个问题的解
分析步骤:
确定状态:dp[i][j]=val
,“到第i个为止,xx为j的方案数/最小代价/最大价值”
状态转移方程:
确定最终状态
要求:
(1)最优子结构
(2)无后效性:已经求解的子问题,不会再受到后续决策的影响。
(3)子问题重叠,将子问题的解存储下来
两种思路:
(1)按题目
线性DP
数字三角形
学习:
(1)将整个大问题分解为一个小问题,就是a[i][j]
位置肯定向max(a[i+1][j],a[i+1][j+1])
的位置走,所以设置状态dp[i][j]
,表示从第i行第j列位置往下走的所有路径的数字和的最大值,可以得到状态转移方程dp[i][j]=a[i][j]+max(dp[i+1][j],dp[i+1][j+1])
,然后自底向上遍历,得到最终状态dp[0][0]
代码:
#include <bits/stdc++.h>using namespace std;const int N=105;
int n,a[N][N],dp[N][N];int main(){ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);cin>>n;for(int i=0;i<n;i++){for(int j=0;j<=i;j++){cin>>a[i][j];}}for(int i=n-1;i>-1;i--){for(int j=0;j<=i;j++){dp[i][j]=a[i][j]+max(dp[i+1][j],dp[i+1][j+1]); //状态转移方程 }}cout<<dp[0][0];return 0;
}
破损的楼梯
学习:
(1)状态dp[i]
表示走到第i级台阶的方案数,可以有第i-1级台阶或者第i-2级台阶走到,所有得到状态转移方程dp[i]=dp[i-1]+dp[i-2]
,得到最终状态dp[n]
,不能从第n级台阶向下写状态转移方程dp[i]=dp[i+1]+dp[i+2]
,因为这样你已经前提假设能走到第n级台阶了,不能走到的情况输出0是错误的
代码:
#include <bits/stdc++.h>using namespace std;
const int N=1e5+10;
const long long p=1e9+7;
int n,m,dp[N]; //状态为从0级台阶走到第i级台阶的方案数
bool mark[N]; //损坏的台阶为true int main(){ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);cin>>n>>m;for(int i=0;i<m;i++){int t;cin>>t;mark[t]=true;}dp[0]=1;//第1级台阶特殊,只能从0级到达 if(!mark[1]) dp[1]=1;for(int i=2;i<=n;i++){//是破损台阶,跳过if(mark[i]) continue;//不是破损台阶,写状态转移方程//第i级台阶可以由第i-1级台阶或者第i-2级台阶到达 //说明第i级台阶的方案数为第i-1级台阶的方案数加第i-2级台阶的方案数之和//破损台阶方案数为0 dp[i]=(dp[i-1]+dp[i-2])%p; }cout<<dp[n];return 0;
}
安全序列
学习思路1:
(1)此题跟上面不一样,dp[i]
表示以i结尾的方案和(这个思想可以学习),比如(1,4)是以4结尾的方案,而(0)就是全都不放的一种方案,所以状态转移方程为 d p [ i ] = ∑ j = 0 i − k − 1 d p [ j ] dp[i]=\sum_{j=0}^{i-k-1} dp[j] dp[i]=j=0∑i−k−1dp[j]
,其中i-k-1>=0才要这样转移,例如k=2时,以4结尾的方案有(0,4)(1,4),而不转移的dp都是1,如(0),(1),再利用前缀和优化 p r e f i x [ i ] = ∑ j = 0 i d p [ j ] prefix[i]=\sum_{j=0}^{i} dp[j] prefix[i]=j=0∑idp[j]
代码:
#include <bits/stdc++.h>using namespace std;typedef long long ll;
const int N=1e6+10,p=1e9+7;
int n,k;
ll dp[N],prefix[N]; //dp[i]表示以i结尾(最后一个放1的位置)的方案个数,状态转移方程为dp[i]=dp[0]+...+dp[i-k-1],所以需要前缀和prefix[i]=dp[0]+..+dp[i]
int main(){ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);cin>>n>>k;//以0结尾的方案个数为1(全不放,全为0) ,prefix[-1]无意义,所以要提前初始化dp[0]=prefix[0]=1;for(int i=1;i<=n;i++){//当i-k-1>=0时,才有状态转移,反之都是1 if(i-k-1<0) dp[i]=1;else dp[i]=prefix[i-k-1]; //不用减prefix[-1]prefix[i]=(prefix[i-1]+dp[i])%p;}cout<<prefix[n]; //结果不是dp[n],不是以n结尾的方案和,而是dp[0]+...+dp[n] return 0;
}
学习思路2:
(1)直接用dp[i]
来表示共i个空位的方案和,而这一位可以放,也可以不放,方案和=第i位不放的方案+第i位放的方案,第i位不放的方案没有限制条件,就是dp[i-1]
,而第i位放的方案与i-k-1和0的大小有关。如果i-k-1<0
,说明不用考虑隔开k个位置的限制,放就是方案数+1,而如果i-k-1>=0
,说明要考虑隔开k个位置的限制,方案数为dp[i-k-1]
,分情况得到状态转移方程
代码:
#include <bits/stdc++.h>using namespace std;typedef long long ll;
const int N=1e6+10,p=1e9+7;
int n,k;
ll dp[N]; //dp[i]表示共i个空位的方案和,等于该位置放+不放的方案和
int main(){ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);cin>>n>>k;dp[0]=1;for(int i=1;i<=n;i++){//当i-k-1<0时,放就是直接加1,不放就是dp[i-1], if(i-k-1<0) dp[i]=(dp[i-1]+1)%p;//当i-k-1>=0时,放的时候才要考虑dp[i-k-1],才有意义,不放就是dp[i-1] else dp[i]=(dp[i-1]+dp[i-k-1])%p; }cout<<dp[n]; return 0;
}
二维DP
dp数组为二维,描述dp状态的变量不止一个
摆花
学习:
(1)状态dp[i][j]
表示到第i种花为止(不一定以第i种花结尾,即不一定摆第i种花),到第j位为止,摆花的方案,因为第i种花可以摆0-a[i]盆,所有dp[i][j]
由dp[i-1][j-k],k=0-a[i]
这些状态转移而来,相加,图示:
![[摆花.png]]转移方程为: d p [ i ] [ j ] = ∑ k = 0 k = a [ i ] a [ i − 1 ] [ j − k ] dp[i][j]=\sum_{k=0}^{k=a[i]}a[i-1][j-k] dp[i][j]=k=0∑k=a[i]a[i−1][j−k]
注意初始状态dp[0][0]=1
,以及k<=min(j,a[i])
,以及不用+=,因为要取模
代码:
#include <bits/stdc++.h>using namespace std;
const int N=105,p=1e6+7;
int n,m,a[N],dp[N][N]; //dp[i][j]表示到第i种花为止,到第j位为止,摆花的方案数 int main(){ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);cin>>n>>m;for(int i=1;i<=n;i++) cin>>a[i];//初始化dp[0][0]=1,不摆也是一种方案dp[0][0]=1;//状态转移方程 for(int i=1;i<=n;i++){ //从第1种花开始,到第n种花 for(int j=0;j<=m;j++){ //从第0位开始,到第m位//状态转移 int t=min(a[i],j); //第i种花最多摆放min(a[i],j]盆 for(int k=0;k<=t;k++){ //第i种花可以摆0-t盆 dp[i][j]=(dp[i][j]+dp[i-1][j-k])%p;//因为要取余,所有不用+= }}} cout<<dp[n][m];return 0;
}
选数异或
学习:
(1)这题跟摆花一样,先区分一下子序列和子串的区别:
子序列不一定要求连续,而子串要求连续,两个都要求顺序跟原来一样
dp[i][j]
表示到第i个数字为止(不一定以第i个数字结尾,即子序列不一定包括第i个数字),到异或和值为j为止的子序列总数
状态转移方程就是第i-1个数字转移到第i个数字,取第i个数字+不取第i个数字的和: d p [ i ] [ j ] = d p [ i − 1 ] [ j ^ a [ i ] ] + d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j\verb|^|a[i]]+dp[i-1][j] dp[i][j]=dp[i−1][j^a[i]]+dp[i−1][j]
(2)dp[0][0]=1
,因为空子序列的异或和是0,是一个子序列方案
(3)题目说0<=a[i]<63
,根据异或性质,异或和不会超过63(63=2^6-1=111111),不管怎么异或都不会超过63,所有能开dp[N][70]
,j也能从0开始遍历到70
代码:
#include <bits/stdc++.h>using namespace std;const int N=1e5+10,p=998244353;
int n,x,a[N],dp[N][70]; //dp[i][j]表示到第i个数字为止(不一定以第i个数字结尾),到值为j为止的子序列个数 int main(){ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);cin>>n>>x;for(int i=1;i<=n;i++) cin>>a[i];//dp初始化,定义是空序列的异或和为0 dp[0][0]=1;for(int i=1;i<=n;i++){for(int j=0;j<70;j++){//状态转移,包括选第i个数和不选第i个数dp[i][j]=(dp[i-1][j^a[i]]+dp[i-1][j])%p;}} cout<<dp[n][x];return 0;
}
数字三角形
学习:
(1)这题跟线性DP的数字三角形有点不一样,多了一个“向左下走的次数与向右下走的次数相差不能超过 1”的要求,所以自底向上dp状态还要加上一个向右走的次数的维度,dp[i][j][k]
表示在(i,j)位置向右走了k次的路径最大和(通过最后状态的k得到结果),最后的状态要对n分奇偶讨论
代码:
#include <bits/stdc++.h>using namespace std;const int N=105;
int n,dp[N][N][55],a[N][N]; //dp[i][j][k]表示在(i,j)位置向右走k次的路径的数字和,相应的向左走的次数为n-i-k int main(){ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);cin>>n;for(int i=1;i<=n;i++){for(int j=1;j<=i;j++){cin>>a[i][j];}}for(int i=n;i>=1;i--){for(int j=1;j<=i;j++){//k从0到n-ifor(int k=0;k<=n-i;k++){//k>=1时,才能向右下走if(k>=1) dp[i][j][k]=a[i][j]+max(dp[i+1][j][k],dp[i+1][j+1][k-1]);//只能向左下走 else dp[i][j][k]=a[i][j]+dp[i+1][j][k];} }}//共走n-1次,分奇偶讨论,保证向左下走的次数与向右下走的次数相差不能超过 1 //n-1为偶数,n为奇数,都一样 if(n%2) cout<<dp[1][1][(n-1)/2];//n-1为奇数,n为偶数,取最大else cout<<max(dp[1][1][(n-1)/2],dp[1][1][n-1-(n-1)/2]);return 0;
}
(2)
学习:正因为有了"向左下走的次数与向右下走的次数相差不能超过 1"的要求,所有可以归纳出n为奇数最后走到(n,n/2+1)位置,而n为偶数,最后走到(n,n/2)或者(n,n/2+1)位置(结果位置已知),所有可以直接自顶向下两个维度得到答案,dp[i][j]
表示在(i,j)位置的路径最大和(仔细思考和原来题的区别)
代码:
#include <bits/stdc++.h>using namespace std;const int N=105;
int n,dp[N][N],a[N][N]; //dp[i][j]表示在(i,j)位置路径的数字和int main(){ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);cin>>n;for(int i=1;i<=n;i++){for(int j=1;j<=i;j++){cin>>a[i][j];}}//初始化 dp[1][1]=a[1][1];for(int i=2;i<=n;i++){for(int j=1;j<=i;j++){//从上转移到下 dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+a[i][j]; //是j-1 }} //n为奇数,就一个位置 if(n%2) cout<<dp[n][n/2+1];else cout<<max(dp[n][n/2],dp[n][n/2+1]);return 0;
}
LIS(最长上升子序列)
要点:
(1)子序列是指按原顺序选出若干不一定连续的元素的子序列,LIS就是该子序列元素是依次递增的,且长度最大。所以,对于固定的数组,虽然LIS序列不一定唯一,但LIS的长度是唯一的
(2)序列元素a[i]
,状态dp[i]
表示以a[i]
结尾的子序列的长度(包括a[i]
),初始状态都为1(自己本身),所以状态转移方程就是i>j,if a[i]>a[j],dp[i]=max(dp[i],dp[j]+1)
,例如:
id: 1 2 3 4 5 6 7 8
a[i]: 1 3 4 2 5 3 7 2
dp[i]: 1 2 3 2 4 3 5 2
从id转移:默认 1 2 1 3 4 5 1
(3)目前只学O(n^2)的LIS,较难的之后再学
蓝桥勇士
学习:
(1)典型的LIS问题,套模版即可
#include<bits/stdc++.h>using namespace std;const int N=1e3+10;
int n,a[N],dp[N],ans=-1;int main(){ios::sync_with_stdio(false);cin>>n;for(int i=1;i<=n;i++){cin>>a[i];dp[i]=1;}for(int i=2;i<=n;i++){for(int j=1;j<i;j++){//战力值超过则加if(a[i]>a[j]) dp[i]=max(dp[i],dp[j]+1);//选最长的 }ans=max(ans,dp[i]);}cout<<ans;return 0;
}
合唱队形
学习:
(1)结果为左边一个最长子序列,右边一个最长子序列,中间i点截断,所以不妨算dpl和dpr,最终枚举i求得最大值
#include <bits/stdc++.h>using namespace std;const int N=105;
int n,a[N],dpl[N],dpr[N];//dpl为从左向右的LIS,dpr为从右向左的LIS,最终枚举i,使得dpl[i]+dp[r]-1最大即可 int main(){ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);cin>>n;for(int i=1;i<=n;i++){cin>>a[i];dpl[i]=1;dpr[i]=1;}//先算dplfor(int i=2;i<=n;i++){for(int j=1;j<i;j++){if(a[i]>a[j]) dpl[i]=max(dpl[i],dpl[j]+1);}} //再算dprfor(int i=n-1;i>=1;i--){for(int j=n;j>i;j--){if(a[i]>a[j]) dpr[i]=max(dpr[i],dpr[j]+1);}} //最后枚举i,算最终答案 int ans=-1;for(int i=1;i<=n;i++){ans=max(ans,dpl[i]+dpr[i]-1);//-1是因为i点算了2次 }cout<<n-ans;//ans为合唱队员数量,结果为出列同学数量 return 0;
}
LCS(最长公共子序列)
学习:
(1)求两个序列A,B的最长公共子序列,只有O(n^2)一种解法
(2)状态dp[i][j]
为A[1-i],B[1-j](不一定以a[i],b[j]结尾,即公共子序列不一定包括a[i],b[j])
时的公共子序列长度,初始值为0,状态转移方程:
if a[i]=b[j] dp[i][j]=dp[i-1][j-1]+1; //相当于把公共元素加入公共子序列,长度加1
else dp[i][j]=max(dp[i-1],dp[j-1]) //相当于向后遍历但不加入公共子序列,长度取最大的
最终状态就是dp[n][m]
,例如:
A:1 3 4 2 5
B:1 4 3 6 2
dp:i:0 1 2 3 4 5
j:
0 0 0 0 0 0 0
1 0 1 1 1 1 1
2 0 1 1 2 2 2
3 0 1 2 2 2 2
4 0 1 2 2 2 2
5 0 1 2 2 3 3
最长公共子序列: 1 4 2
(3)求完dp数组,再回过来求最长公共子序列元素的方法:
从(n,m)开始回溯,直到跳出边界停止
if a[i]=b[j] 说明从左上角来,回到(i-1,j-1),得到一个最长公共子序列元素
else 说明是从上方或者左侧最大的方法而来if(dp[i-1][j]>=dp[i][j-1]) 回到(i-1,j)(=默认向上走)else 回到(i,j-1)
最长公共子序列
学习:
(1)模版题
代码:
#include <bits/stdc++.h>
#include <algorithm>using namespace std;const int N=1e3+10;
int n,m,a[N],b[N],dp[N][N];
vector<int> v; //记录最长公共子序列
int main(){ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);cin>>n>>m;for(int i=1;i<=n;i++) cin>>a[i];for(int j=1;j<=m;j++) cin>>b[j];for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){//加入公共子序列 if(a[i]==b[j]) dp[i][j]=dp[i-1][j-1]+1;//不加入 else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);}}cout<<dp[n][m]<<endl;//打印一个最长公共子序列int i=n,j=m; //起点while(i && j){//是最长公共子序列元素if(a[i]==b[j]){v.emplace_back(a[i]);i=i-1,j=j-1;}//不是else{if(dp[i-1][j]>=dp[i][j-1]) i=i-1;else j=j-1;} }//反转v,为最长公共子序列元素顺序reverse(v.begin(),v.end());for(auto &x:v){cout<<x<<" ";}return 0;
}
真题
接龙数列
学习:
(1)这题跟最长上升子序列(LIS)类似,只是判断条件不同罢了,记住dp[i]
是以a[i]
结尾(不是到a[i]
为止不包括a[i]
那种),但是只能拿到50分
(2)cin
从标准输入读取的数据最初都是以 字符序列 的形式存在的,具体是什么类型是自己定义转换得来的,所以这题要获得一个数字的首位和尾位,不用写函数对整数操作,直接把数字当做一个字符串输入,提取首位和尾位即可,不过记得减’0’:
string s;
cin>>s;
f[i]=s[0]-'0';
e[i]=s[s.size()-1]-'0';
代码:
#include <bits/stdc++.h>using namespace std;
const int N=1e5+10;
int n,f[N],e[N],dp[N];//f数组记录首位数字,e数组记录末尾数字,dp[i]表示到第i个数字为止(以a[i]为结尾),最长接龙数列的长度 int main(){ios::sync_with_stdio(false);cin>>n;for(int i=1;i<=n;i++){string s;cin>>s;f[i]=s[0]-'0';e[i]=s[s.size()-1]-'0';dp[i]=1;}int maxn=1;for(int i=2;i<=n;i++){for(int j=1;j<i;j++){//状态转移 if(e[j]==f[i]){dp[i]=max(dp[i],dp[j]+1);}}maxn=max(maxn,dp[i]);}cout<<n-maxn;return 0;
}
优化学习:
(1)因为这题的条件就是把末位数字等与首位数字的两个数字连接起来,本质上就是一个末位数字的状态转移到另一个末位数字的状态,dp[i]
表示以i数字结尾的最长接龙数列,因为数字为0-9,所以dp[10]
即可,状态转移方程:
dp[b]=max(dp[b],dp[a]+1)(b为第i个数字的末位数字,a为第i个数字的首位数字,即前面某个数字的末位数字)
2023有奖问答
学习:
(1)dp填空题,dp[i][j]
表示到第i题为止,到分数j为止的方案数,状态转移方程:
dp[i][0]=dp[i-1][0]+dp[i-1][10]+...+dp[i-1][90]
dp[i][j]=dp[i-1][j-10] //不用+1,因为表示方案数,状态转移过来这是一种方案,j>=10
(2)根据实际意义初始化dp:
dp[1][0]=dp[1][10]=1;//1道题只有0分和10分两种状态
代码:
#include <bits/stdc++.h>using namespace std;
typedef long long ll;
ll dp[35][101];//dp[i][j]表示到第i题为止,到分数j为止的方案数 int main(){ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);//根据实际意义初始化 dp[1][0]=dp[1][10]=1;//1道题只有0分和10分两种状态ll ans=0;for(int i=2;i<=30;i++){//dp[i][0]转移 for(int j=0;j<=90;j+=10){dp[i][0]+=dp[i-1][j];}//dp[i][j]转移for(int j=10;j<=100;j+=10){dp[i][j]=dp[i-1][j-10];//不用+1,因为表示方案数,状态转移过来对于总体来看是一种方案 } }for(int i=1;i<=30;i++){ans+=dp[i][70];}cout<<ans;//ans=8335366return 0;
}
填空题暴力dfs代码:
#include <bits/stdc++.h>using namespace std;
typedef long long ll;
ll ans=0;//x为题目数量,y为分数
void dfs(int x,int y){//递归中止条件 if(y==100 || x>30) return;//x>30,x可以=30,此时y可能为70 if(y==70) ans++;//只要遇到70就加,当做中途放弃dfs(x+1,0);dfs(x+1,y+10);
}int main(){ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);dfs(0,0);cout<<ans;//ans=8335366return 0;
}
2022积木画
学习:
(1)这题导致状态转移的原因是添加了一次积木(不一定是1个),而积木又分I型和L型,所以当前状态可以从添加I型积木前的状态1和添加L型积木前的状态2转移过来(类似于爬楼梯),当前状态又可能出现两行都有积木、第一行积木比第二行多一个、第二行比第一行多一个三种状态(如何想的过程),所以定义dp[i][j]
表示到第i列为止,j=0,1,2分别表示三种状态,的总方案数,状态转移如下图所示
![[积木画.png]]代码:
#include <bits/stdc++.h>using namespace std;
const int N=1e7+10,p=1000000007;
int n,dp[N][3];//dp[i][j]表示到画布第i列为止的方案数,j=0为第一行比第二行多一个,j=1表示两行都一个,j=2表示第二行比第一行多一个 int main(){ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);cin>>n;//先初始化第1列和第2列 dp[1][1]=1,dp[2][0]=dp[2][2]=1,dp[2][1]=2;for(int i=3;i<=n;i++){//核心状态转移,表示添加I型积木或者L型积木转移过来 //dp[i][0],即第i列第一行比第二行多一个dp[i][0]=(dp[i-1][2]+dp[i-2][1])%p;//dp[i][2],和dp[i][0]类似,反过来dp[i][2]=(dp[i-1][0]+dp[i-2][1])%p;//dp[i][1]比较特殊,添加两种类型积木各有两种转移,分类讨论dp[i][1]=( (dp[i-1][1]+dp[i-2][1])%p + (dp[i-1][0]+dp[i-1][2])%p)%p;//三个取余都不能少,位置也不能不对 }cout<<dp[n][1];return 0;
}
李白打酒加强版
学习:
(1)dp问题想好1.状态2.状态转移方程3.什么条件下转移哪些状态(状态的累加)4.最终状态
![[李白打酒加强版1.png]]![[李白打酒加强版2.png]]
所以一共会有三种状态转移,而利用dp[i][j][k]=(dp[i][j][k]+某种状态方案数)%p
可以保证方案数是累加的
(2)本题要求最后一次必遇花,就是求dp[n][m-1][1]
,同时告诉你遍历到m-1,且酒的量不超过m(因为只能减m*1升),且k>=1,因为最后一次要减1
(3)都从0开始遍历,因为要赋只遇店的和只遇花的值,加上条件判断控制状态转移即可,而不是给0赋一些值(自己考虑不周全),并从1开始遍历(错误方法)
代码:
#include <bits/stdc++.h>using namespace std;
const int p=1e9+7;
int n,m,dp[105][105][105];//dp[i][j][k]表示到遇店i次为止,遇花j次为止,酒k升为止的方案次数 int main(){ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);cin>>n>>m;dp[0][0][2]=1;for(int i=0;i<=n;i++){ //必须从0开始,因为i=0或者j=0有很多种情况 for(int j=0;j<=m-1;j++){ //遇花m-1次,最后一次必遇花 for(int k=0;k<=m;k++){ //因为最后要为0,遇花是减1,所以酒的大小不会超过m //遇店 if(i>=1 && k%2==0) dp[i][j][k]=(dp[i][j][k]+dp[i-1][j][k/2])%p;//遇花(方案累加,加上dp[i][j][k],如果前面遇店,就等价于加上遇店) if(j>=1 && k>=1) dp[i][j][k]=(dp[i][j][k]+dp[i][j-1][k+1])%p; //k>=1,因为最后一次是遇花,k必须大于等于1/* //等价于下面的方法//遇店 if(i>=1 && k%2==0) dp[i][j][k]=dp[i-1][j][k/2];//遇花if(j>=1 && k>=1) dp[i][j][k]=dp[i][j-1][k+1]; //k>=1,因为最后一次是遇花,k必须大于等于1//遇店+遇花if(i>=1 && k%2==0 && j>=1 && k>=1) dp[i][j][k]=(dp[i-1][j][k/2]+dp[i][j-1][k+1])%p;*/}}}cout<<dp[n][m-1][1];//保证最后一次是花,就是求dp[n][m-1][1] return 0;
}
dfs+记忆化+剪枝:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 100 + 7;
const int mod = 1e9 + 7;
int dp[maxn][maxn][maxn];
int dfs(int x, int y, int z) // 酒量、遇见店次数、遇见花次数
{if (x < 0 || y < 0 || z < 0) return 0; // 不合法if (x > z) return 0; // 酒量不可能大于剩余"遇见花的次数"if (z == 1) return y == 0 && x == 1; // 最后一次必须遇到的是花 && 酒量只剩1if (dp[x][y][z] != -1) return dp[x][y][z];dp[x][y][z] = (dfs(x * 2, y - 1, z) + dfs(x - 1, y, z - 1)) % mod;return dp[x][y][z];
}
int main()
{memset(dp, -1, sizeof dp);int n, m; cin >> n >> m;cout << dfs(2, n, m) << endl;return 0;
}
相关文章:
蓝桥杯备赛Day12 动态规划1基础
动态规划 动态规划基础 动态规划将复杂问题分解成很多重叠的子问题,再通过子问题的解得到整个问题的解 分析步骤: 确定状态:dp[i][j]val,“到第i个为止,xx为j的方案数/最小代价/最大价值” 状态转移方程: 确定最终状态 要求: (1)最优子结构 (2)无后效性…...
我的AI工具箱Tauri版-通用音频转文本
本模块支持FunAsr和FasterWhisper两种模式,可批量处理音频与视频文件,自动生成txt文本与srt字幕,满足多种应用场景需求。 工具内置FunAsr,无需额外参数调整,特别适用于中文语音的高质量转录,确保识别准确率…...
C#—Settings配置详解
C#—Settings配置详解 在C#项目中,全局配置通常指的是应用程序的设置(settings),这些设置可以跨多个类或组件使用,并且通常用于存储应用程序的配置信息,如数据库连接字符串、用户偏好设置等。 Settings配置…...
机器学习算法——分类任务
算法: 1、决策树 2、随机森林 3、梯度提升树 4、逻辑回归 5、支持向量机SVM 6、K近邻 KNN 7、朴素贝叶斯 8、多层感知机 9、统一分类 10、比较总结 11、完整代码 1、决策树 1.1 Decision Tree Analysis (C4.5,CART,CHAID)决策树 算法树结构特征选择连续值处理缺失…...
聆听PostgreSQL数据库的使用
参考:(1)零基础入门PostgreSQL教程 (2)菜鸟教程 文章目录 一、PostgreSQL是什么?二、基本使用1.下载2.操作(1)数据库(2)表 一、PostgreSQL是什么?…...
C# 装箱(Boxing)与拆箱(Unboxing)
C# 装箱(Boxing)与拆箱(Unboxing) 在 C# 中,装箱和拆箱是与值类型(如结构体)和引用类型(如类)之间的转换相关的操作。它们是类型系统的一部分,但如果不正确使…...
vue实例
// vue应用通过createApp函数创建一个新的应用实例,相当于根组件 import { createApp } from vue import App from ./App.vue // 在一个vue项目当中,有且只有一个vue的实例对象 const appcreateApp(App) // App:根组件 // 实例必须调用了.mount&am…...
Spring Boot的启动流程
Spring Boot 的启动流程是一个复杂且有序的过程: 创建SpringApplication实例 — 调用run方法 — 启动完成(发布应用启动事件,配置环境,创建ApplicationContext,准备ApplicationContext,刷新ApplicationContext[【创建B…...
springboot整合pagehelper实现mybatis分页
1.依赖 <dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.4.0</version></dependency><dependency><groupId>com.github.pagehelper<…...
Qt信号与槽机制
Qt信号与槽机制(Signal and Slot Mechanism)是Qt框架中用于对象间通信的一种机制。信号和槽是Qt的核心特性之一,它们允许对象在特定事件发生时发送信号,并由其他对象通过槽函数进行响应。这种机制不仅简化了对象间的通信&…...
Qt空项目代码解释
一、 背景 创建的是一个 QWidget 项目。 二、main.cpp 1、图片 2、代码解释 (1)QApplication Qt 图形化界面中一定有 QApplication (2)Widget w; 是 QWidget 的子类。 (3)w.show(); 继承父类的显示…...
【Git】版本控制系统Git命令详解
2024.06.06 2024.06.06\ 2024.06.06 Resources 强推:Pro Git - Book (git-scm.com).中文版. 强烈推荐网址:https://learngitbranching.js.org/?localezh_CN. LearnGit Game: 基础(Git 主要命令) Git Commit&#…...
Java【多线程】(2)线程属性与线程安全
目录 1.前言 2.正文 2.1线程的进阶实现 2.2线程的核心属性 2.3线程安全 2.3.1线程安全问题的原因 2.3.2加锁和互斥 2.3.3可重入(如何自己实现可重入锁) 2.4.4死锁(三种情况) 2.4.4.1第一种情况 2.4.4.2第二种情况 2.4…...
浅克隆与深克隆区别
package d12_api_object;public class Test2 {public static void main(String[] args) throws CloneNotSupportedException {//目标:掌握Object类提供的对象克隆方法//1、protected Object clone():对象克隆User u1 new User(1,"min","1120",…...
【计算机网络入门】初学计算机网络(九)
目录 1.令牌传递协议 2. 局域网&IEEE802 2.1 局域网基本概念和体系结构 3. 以太网&IEEE802.3 3.1 MAC层标准 3.1.1 以太网V2标准 编辑 3.2 单播广播 3.3 冲突域广播域 4. 虚拟局域网VLAN 1.令牌传递协议 先回顾一下令牌环网技术,多个主机形成…...
数列极限入门习题
数列极限入门习题 lim n → ∞ ( 1 1 2 1 3 ⋯ 1 n ) 1 n \lim\limits_{n\rightarrow\infty}(1 \frac{1}{2}\frac{1}{3}\cdots\frac{1}{n})^{\frac{1}{n}} n→∞lim(12131⋯n1)n1 lim n → ∞ ( 1 n 1 1 n 2 ⋯ 1 n n ) \lim\limits_{n\rightarrow\…...
【决策树】分类属性的选择
文章目录 1.信息增益(ID3)2.信息增益率(C4.5)3.基尼指数(CART)ps.三者对比 实现决策树算法最关键的一点就是如何从所有的特征属性中选择一个最优的属性对样本进行分类,这种最优可以理解为希望划…...
Mysql面试篇笔记:
优化: 1.如何定位慢查询: 首先压测接口,查看那个接口比较慢,可以通过多种工具,比如Skywaking 可以查看各个接口响应时间,查看接口最慢,然后去跟踪接口,查看详细信息&#…...
005-Docker 安装 Redis
Docker 安装 Redis 1.从镜像官网拉取Redis镜像2.创建实例并启动3.测试连接4.设置开机启动 1.从镜像官网拉取Redis镜像 镜像官网地址:https://hub.docker.com执行命令 -- 拉取最新的版本 docker pull redis查看镜像 docker images2.创建实例并启动 先创建好需要的…...
可终身授权的外国工具,不限次数使用!PDF转CAD的软件
最近有不少朋友问我有没有好用的CAD转换工具,今天就来给大家分享两款超实用的小软件,希望能帮到大家。 第一款软件是一款国外开发的,它专门用来把PDF文件转换成CAD格式,特别方便。 这款软件的操作非常简单,打开后无需安…...
GaussDB性能调优技术指南
一、性能调优核心目标 降低响应时间:缩短单次查询或事务的处理时间(如从秒级优化到毫秒级)。 提高吞吐量:支撑更高并发请求(如从千次/秒提升到百万次/秒)。 资源高效利用:减少 CPU、…...
iOS逆向工程专栏 第13篇:iOS动态分析基础
iOS逆向工程专栏 第13篇:iOS动态分析基础 引言 在前面的文章中,我们详细探讨了iOS系统架构、逆向开发环境搭建、Mach-O文件格式分析,以及各种静态分析工具和技术。通过静态分析,我们可以了解应用的结构、类和方法定义,以及基本的控制流程。然而,静态分析也存在明显的局…...
【现代深度学习技术】卷积神经网络03:填充和步幅
【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈PyTorch深度学习 ⌋ ⌋ ⌋ 深度学习 (DL, Deep Learning) 特指基于深层神经网络模型和方法的机器学习。它是在统计机器学习、人工神经网络等算法模型基础上,结合当代大数据和大算力的发展而发展出来的。深度学习最重…...
(链表 删除链表的倒数第N个结点)leetcode 19
设空结点指向head便于插入和删除结点 考虑特殊情况 head结点被删除 a结点仅用来测试长度,找到目标结点的位置 b结点为空结点指向head返回值 cur用来删除目标值(特殊情况 目标值为head 这时curb) 则开始就将cur初始化为b开始遍历 /*** Definition fo…...
初阶数据结构(C语言实现)——3顺序表和链表(2)
2.3 数组相关面试题 原地移除数组中所有的元素val,要求时间复杂度为O(N),空间复杂度为O(1)。OJ链接 力扣OJ链接-移除元素删除排序数组中的重复项。力扣OJ链接-删除有序数组中的重复项合并两个有序数组。力扣OJ链接-合并两个有序数组 2.3.1 移除元素 1…...
leetcode 138. 随机链表的复制
题目如下 数据范围 这道题十分好,一定要自己写看看再来看别人的答案! 首先复制题目给出的链表,对于每个新生成的node利用名为ri的map记录它们在链表的位置和指针。 接着利用名为rd的map存储每个链表中random对应的位置比如(0,…...
【OpenCV C++】以时间命名存图,自动检查存储目录,若不存在自动创建, 按下空格、回车、Q、S自动存图
文章目录 // 保存图像的函数 void saveImage(const cv::Mat& frame) {// 生成唯一文件名auto now = std::chrono::system_clock::...
C# OnnxRuntime部署DAMO-YOLO人头检测
目录 说明 效果 模型信息 项目 代码 下载 参考 说明 效果 模型信息 Model Properties ------------------------- --------------------------------------------------------------- Inputs ------------------------- name:input tensor:Floa…...
DDD该怎么去落地实现(4)多对多关系
多对多关系的设计实现 如题,DDD该如何落地呢?前面我通过三期的内容,讲解了DDD落地的关键在于“关系”,也就是通过前面我们对业务的理解先形成领域模型,然后将领域模型的原貌,形成程序代码中的服务、实体、…...
Vue 3 组件库开发实战:打造基础 UI 组件库并发布 - 构建可复用的 Vue 组件资产
引言 欢迎再次回到 Vue 3 + 现代前端工程化 系列技术博客! 在昨天的第六篇博客中,我们深入探索了 Vue 3 Composition API 的进阶应用,通过构建可拖拽看板应用,熟练掌握了自定义 Hook 的代码复用技巧。 今天,我们将迈向 Vue 3 组件化开发的更高阶段,聚焦于 组件库的开发与…...
UNION 和 UNION ALL 的区别:深入解析 SQL 中的合并操作
在 SQL 的世界里,当我们需要合并多个查询结果集时,UNION和UNION ALL是两个常用的操作符。虽然它们的功能看起来相似,但实际上有着重要的区别,这些区别在不同的应用场景中会对查询结果和性能产生显著影响。本文将详细探讨UNION和UN…...
Redis 哈希(Hash)
Redis 哈希(Hash) 概述 Redis 哈希(Hash)是一种特殊的键值对类型,它允许存储结构化的数据,例如一个对象或记录。每个哈希值可以包含多个字段,每个字段又可以存储一个字符串值。这使得Redis哈希非常适合用于存储对象的…...
Android Activity栈关系解析
在 Android 系统中,这些类共同构成了 Activity 任务栈管理的核心架构。它们的关系可以类比为一栋大楼的管理体系,每个类负责不同层级的任务。以下是它们的详细解释和实际场景示例: 1. ActivityRecord(活动记录) 是什么…...
7.1.2 计算机网络的分类
文章目录 分布范围交换方式 分布范围 计算机网络按照分布范围可分为局域网、广域网、城域网。局域网的范围在10m~1km,例如校园网,网速高,主要用于共享网络资源,拓扑结构简单,约束少。广域网的范围在100km,例…...
Arcgis中添加脚本工具箱
准备资料 (1)工具箱 (2)python脚本 1、打开arcmap 2、找到目录窗口 3、复制粘贴工具箱的路径 4、添加或者确认python脚本路径 脚本上右键属性(注意:脚本内容和路径最后都不要有中文,否则可能报错) 如果…...
【Python 数据结构 1.零基础复习】
目录 一、输入与输出 1.输入 2.格式化输出 二、数字与变量 1.字符串 & 整型 2.字符串 & 整型 & 浮点型 3.变量 练习 2235. 两整数相加 三、运算与操作 1.四则运算 练习 2769. 找出最大的可达成数字 3.取整与取余 练习 2651. 计算列车到站时间 编辑 四、真与假 1…...
颠覆NLP的魔法:深度解读Transformer架构及其核心组件
目录 颠覆NLP的魔法:深度解读Transformer架构及其核心组件 一、Transformer 架构概述 二、核心组件解析 1. Self-Attention(自注意力机制) 2. 位置编码(Positional Encoding) 3. 多头注意力(Multi-Hea…...
【pytest框架源码分析二】pluggy源码分析之add_hookspecs和register
这里我们看一下_manager.py里的类和方法,最主要的是PluginManager类,类的初始化函数如下: class PluginManager:"""Core class which manages registration of plugin objects and 1:N hookcalling.You can register new hoo…...
【leetcode hot 100 53】最大子数组和
解法一:(动态规划)我们用 f(i) 代表以第 i 个数结尾的「连续子数组的最大和」,那么很显然我们要求的答案就是:max{f(i)},f(i)max{f(i−1)nums[i],nums[i]} class Solution {public int maxSubArray(int[] …...
Sqlserver安全篇之_启用TLS即配置SQL Server 数据库引擎以加密连接
官方文档 https://learn.microsoft.com/zh-cn/sql/database-engine/configure-windows/configure-sql-server-encryption?viewsql-server-ver16 https://learn.microsoft.com/zh-cn/sql/database-engine/configure-windows/manage-certificates?viewsql-server-ver15&pre…...
009---基于Verilog HDL的单比特信号边沿检测
文章目录 摘要一、边沿检测二、时序逻辑实现2.1 rtl2.2 tb 三、组合逻辑实现3.1 rtl3.2 tb 摘要 文章为学习记录。采用时序逻辑和组合逻辑实现边沿检测的核心逻辑。组合逻辑实现的上升沿和下降沿的脉冲比时序逻辑实现的上升沿和下降沿的脉冲提前一拍。 一、边沿检测 边沿检测…...
istio的核心概念简介
Istio 是一个开源的服务网格(Service Mesh)平台,旨在帮助管理、连接、保护和观察分布式微服务架构中的服务。它最初由 Google、IBM 和 Lyft 合作开发,广泛应用于 Kubernetes 环境。Istio 的核心目标是通过提供统一的流量管理、安全…...
如何在Apple不再支持的MacOS上安装Homebrew
手头有一台2012年产的Macbook Pro,系统版本停留在了10.15.7(2020年9月24日发布的)。MacOS 11及后续的版本都无法安装到这台老旧的电脑上。想通过pkg安装Homebrew,发现Homebrew releases里最新的pkg安装包不支持MacOS 10.15.7&…...
@update 的常见用法 Vue.js
在 Vue.js 中,update 是一个事件监听器,通常用于监听自定义组件或某些 Vue 原生组件(如 <input> 或自定义组件)的更新事件。它并不是 Vue 的核心 API,而是一种约定俗成的命名方式,用于处理组件内部状…...
C#开发——日期操作类DateTime
在C#中,日期和时间的操作主要通过 System.DateTime 类来实现。 DateTime 提供了丰富的属性和法,用于处理日期和时间的创建、格式化、比较和计算等操作。以下是一些常用的日期函数和特性: 一、创建日期和时间 1、直接指定日期和时间&…...
大语言模型学习--LangChain
LangChain基本概念 ReAct学习资料 https://zhuanlan.zhihu.com/p/660951271 LangChain官网地址 Introduction | 🦜️🔗 LangChain LangChain是一个基于语言模型开发应用程序的框架。它可以实现以下应用程序: 数据感知:将语言模型…...
Oracle数据库安全防护体系构建与核心技术解析
引言:从某跨国集团数据泄露事件看Oracle防护困局 2025年1月,某跨国零售企业Oracle数据库遭APT组织"暗夜猎手"攻击,攻击者通过三重渗透路径实现数据窃取: 存储层突破:利用Oracle TDE密钥管理漏洞获取wallet…...
iOS UICollectionViewCell 点击事件自动化埋点
iOS 中经常要进行埋点,我们这里支持 UICollectionViewCell. 进行自动化埋点,思路: 通过hook UICollectionViewCell 的setSelected:方法, 则新的方法中执行埋点逻辑,并调用原来的方法 直接上代码 implementation UICol…...
软件工程---基于构件的软件工程
基于构件的软件工程(CBSE)是一种软件开发方法,通过重用现有的软件构件来构建系统,从而提高开发效率和软件质量。这种方法强调软件系统的模块化设计和构建复用,使得软件开发过程更加高效和灵活。 企业软件开发…...
Redis--单线程模型
目录 一、引言 二、Redis单线程模型 三、原因 四、为什么redis是单线程模型,但他的速度这么快? 五、总结 一、引言 本篇文章就Redis为什么是单线程模型做简单介绍。 二、Redis单线程模型 redis只使用一个线程,处理所有的命令请求&#x…...