动态规划
解题步骤 :
第一步:确定状态
确定dp一维还是二维 dp[i]或dp[i][j]的含义
第二步:转移方程
由上一步推出的公式 由上一步确定此步骤的公式
第三步:初始条件和边界情况
初始化:一般方案数 初始化dp[0]=1或dp[0][0]=1
第四步:确定计算顺序
由正序推出 还是逆序推出
01背包 一维逆序
完全背包 一维 正序
多重背包 01背包的n次循环
题型一:LIS
子序列中,按照原顺序选出若干个不一定连续的元素所组成的序列
蓝桥勇士(板子)
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
typedef long long ll;
int n;
ll a[N];
int dp[N];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
dp[i]=1;
for(int j=1;j<i;j++){
if(a[j]<a[i]) dp[i]=max(dp[i],dp[j]+1);
}
}
int ans=0;
for(int i=1;i<=n;i++){
ans=max(ans,dp[i]);
}
cout<<ans<<endl;
return 0;
}
最长上升子序列之和
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1010;
int a[N],f[N];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
f[i]=a[i];
for(int j=1;j<i;j++){
if(a[j]<a[i]){
f[i]=max(f[i],f[j]+a[i]);
}
}
}
int res=0;
for(int i=1;i<=n;i++){
res=max(res,f[i]);
}
cout<<res;
return 0;
}
怪盗基德的滑翔伞
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=110;
int f[N];
int w[N];
int T;
int main(){
cin>>T;
while(T--){
int m;
cin>>m;
for(int i=0;i<m;i++){
cin>>w[i];
}
int res=0;
for(int i=0;i<m;i++){
f[i]=1;
for(int j=0;j<i;j++){
if(w[i]>w[j]){
f[i]=max(f[i],f[j]+1);
}
}
res=max(res,f[i]);
}
}
}
登山
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1010;
int h[N],f[N],g[N];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>h[i];
}
for(int i=1;i<=n;i++){
f[i]=1;
for(int j=1;j<i;j++){
if(h[j]<h[i]){
f[i]=max(f[i],f[j]+1);
}
}
}
for(int i=n;i>=1;i--){
g[i]=1;
for(int j=n;j>i;j--){
if(h[j]<h[i]){
g[i]=max(g[i],g[j]+1);
}
}
}
int res=0;
for(int i=1;i<=n;i++){
res=max(res,f[i]+g[i]-1);
}
cout<<res;
return 0;
}
题型二:LCS
两个序列A和B,求他们的最长公共子序列
最长公共子序列(板子)
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m;
int a[N],b[N];
int dp[N][N];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=m;i++){
cin>>b[i];
}
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;
return 0;
}
题型三:01背包
每一种物品只有两种状态 “拿”或“不拿”
小明的背包1(板子)
二维
#include<bits/stdc++.h>
using namespace std;
const int N=110;
const int M=1010;
typedef long long ll;
ll dp[N][N];
int main(){
int n,V;
cin>>n>>V;
for(int i=1;i<=n;i++){
ll w,v;
cin>>w>>v;
for(int j=0;j<=V;j++){
if(j>=w) dp[i][j]=max(dp[i][j],dp[i-1][j-w]+v);
else dp[i][j]=dp[i-1][j];
}
}
cout<<dp[n][V]<<endl;
return 0;
}
一维
#include<bits/stdc++.h>
using namespace std;
const int N=110;
const int M=1010;
typedef long long ll;
ll dp[M];
int main(){
int n,V;
cin>>n>>V;
for(int i=1;i<=n;i++){
ll w,v;
cin>>w>>v;
for(int j=V;j>=w;j--){
dp[j]=max(dp[j],dp[j-w]+v);
}
}
cout<<dp[V]<<endl;
return 0;
}
题型四:完全背包
每一种物品只有无穷种状态即“拿0个 1个……无穷个”
小明的背包2(板子)
#include <iostream>
using namespace std;
const int N=1e3+10;
int dp[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
int w,v;
cin>>w>>v;
for(int j=w;j<=m;j++){
dp[j]=max(dp[j],dp[j-w]+v);
}
}
cout<<dp[m];
return 0;
}
题型五:多重背包
每种物品有s个 每一种物品只有s+1状态
只需要在01背包的基础上对于每个物品循环更新s次
小明的背包3(板子)
#include<bits/stdc++.h>
using namespace std;
const int N=205;
int dp[N];
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
int w,v,s;
cin>>w>>v>>s;
while(s--){
for(int j=m;j>=w;j--){
dp[j]=max(dp[j],dp[j-w]+v);
}
}
}
cout<<dp[m]<<endl;
return 0;
}
题型六:数字三角形模型
数字三角形
#include<bits/stdc++.h>
using namespace std;
const int N=105;
int n;
int a[N][N],dp[N][N];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j];
}
}
for(int i=n;i>=1;i--){
for(int j=1;j<=i;j++){
dp[i][j]=a[i][j]+max(dp[i+1][j],dp[i+1][j+1]);
}
}
cout<<dp[1][1]<<endl;
return 0;
}
摘花生
max:不用特判起点以及初始化
min:特判起点并且初始化1e9
#include<iostream>
using namespace std;
const int N = 105;
int a[N][N], f[N][N];
int q, row, col;
int main(){
cin >> q;
while(q--){
cin >> row >> col;
for(int i = 1; i <= row; i++){
for(int j = 1; j <= col; j++){
cin >> a[i][j];
}
}
//f[i][j]指的是到(i, j)的最大花生数
for(int i = 1; i <= row; i++){
for(int j = 1; j <= col; j++){
f[i][j] = max(f[i-1][j], f[i][j-1]) + a[i][j];
}
}
cout << f[row][col] << endl;
}
return 0;
}
最低通行费
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int a[N][N];
int dp[N][N];
int n;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j];
}
}
//求最小值
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==1&&j==1) dp[1][1]=a[1][1];
else{
dp[i][j]=1e9;
if(i>1) dp[i][j]=min(dp[i][j],dp[i-1][j]+a[i][j]);
if(j>1) dp[i][j]=min(dp[i][j],dp[i][j-1]+a[i][j]);
}
}
}
cout<<dp[n][n]<<endl;
return 0;
}
题型七:状态DP
解题步骤
1.存放原状态
2.存放满足条件check()的状态 放入state中 并且利用count计算1的个数
3.寻找可以转移到满足条件状态的前状态 head[i].push_back(j)
4.动归初始化为1
5.第一个循环层数 循环state.size() 判断条件 遍历state状态的前状态 k:state[j]
6.进行动态规划转移方程 f[i][j][c]+=f[i-1][j-a][b]
小国王
//f[i][j][s]状态表示 所有只排在前i行,已经摆了j个国王 并且第i行的摆放状态s的所有方案
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=12,M=1<<10,k=110;
int n,m;
vector<int> state;//所有合法的状态
vector<int> head[M];//每个状态可以转移到的状态
int cnt[M];//每个状态的1的个数
ll f[N][k][M];
int count(int state){
int res=0;
for(int i=0;i<n;i++){
res+=state>>i&1;//判断1的个数
}
return res;
}
bool check(int state){//是否存在连续的1
for(int i=0;i<n;i++){
if((state>>i&1)&&(state>>(i+1)&1)){
return false;
}
}
return true;
}
int main(){
cin>>n>>m;
for(int i=0;i<1<<n;i++){
if(check(i)){
state.push_back(i);
cnt[i]=count(i);
}
}
for(int i=0;i<state.size();i++){
for(int j=0;j<state.size();j++){
int a=state[i],b=state[j];
if((a&b)==0&&check(a|b)){//上下不可有同列的1 两行不可有相邻
head[i].push_back(j);
}
}
}
//动归初始化 算一种方案
f[0][0][0]=1;
for(int i=1;i<=n+1;i++){
for(int j=0;j<=m;j++){//摆放国王的个数
for(int a=0;a<state.size();a++){//枚举合法状态
for(auto b:head[a]){//枚举可以转移到此合法状态的状态
int c=cnt[state[a]];//状态的1的个数 状态a下摆放的国王个数
if(j>=c){
f[i][j][a]+=f[i-1][j-c][b];
}
}
}
}
}
cout<<f[n+1][m][0]<<endl;//要保证第n行摆放的状态的多样性
return 0;
}
玉米田
//f[i][j]:已经摆完前i行,并且第i行的状态j的所有摆放方案
#include<bits/stdc++.h>
using namespace std;
const int N=15,M=1<<N,mod=1e8;
int n,m;
int w[N];
vector<int> state;
vector<int> head[M];
int f[N][N];
bool check(int state){
for(int i=0;i+1<m;i++){
if((state>>i&1)&&(state>>(i+1)&1)) return false;
}
return true;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=0;j<m;j++){
int t;
cin>>t;
w[i]+=!t*(1<<j);//转化为十进制 1变成0 只有0时候才被录入
}
}
for(int i=0;i<1<<m;i++){
if(check(i)) state.push_back(i);//合法状态
}
for(int i=0;i<state.size();i++){
for(int j=0;j<state.size();j++){
int a=state[i],b=state[j];
if((a&b)==0){//四个方向
head[i].push_back(j);
}
}
}
f[0][0]=1;
for(int i=1;i<=n+1;i++){
for(int j=0;j<state.size();j++){
if((state[j]&w[i])==0){//排除1&1=1(贫瘠地方不可种)
for(int k:head[j]){
f[i][j]=(f[i][j]+f[i-1][k])%mod;
}
}
}
}
cout<<f[n+1][0]<<endl;
return 0;
}
炮兵阵地
//f[i][j][k]表示所有已经摆完前i行,且第i-1行为j 第i行状态为k 摆放的最大值
//连续三行不可有炮
//意大利炮不可放山地 只可放平地
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 10, M = 1 << 10;
int n, m;
int g[1010];
int f[2][M][M];
vector<int> state;
int cnt[M];
bool check(int state)
{
for (int i = 0; i < m; i ++ )
if ((state >> i & 1) && ((state >> i + 1 & 1) || (state >> i + 2 & 1)))
return false;
return true;
}
int count(int state)
{
int res = 0;
for (int i = 0; i < m; i ++ )
if (state >> i & 1)
res ++ ;
return res;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
for (int j = 0; j < m; j ++ )
{
char c;
cin >> c;
g[i] += (c == 'H') << j;
}
for (int i = 0; i < 1 << m; i ++ )
if (check(i))
{
state.push_back(i);
cnt[i] = count(i);
}
for (int i = 1; i <= n; i ++ )
for (int j = 0; j < state.size(); j ++ )
for (int k = 0; k < state.size(); k ++ )
for (int u = 0; u < state.size(); u ++ )
{
int a = state[j], b = state[k], c = state[u];
if (a & b | a & c | b & c) continue;
if (g[i] & b | g[i - 1] & a) continue;
f[i & 1][j][k] = max(f[i & 1][j][k], f[i - 1 & 1][u][j] + cnt[b]);
}
int res = 0;
for (int i = 0; i < state.size(); i ++ )
for (int j = 0; j < state.size(); j ++ )
res = max(res, f[n & 1][i][j]);
cout << res << endl;
return 0;
}
题型八: 区间DP
//对于区间[i,j],其子区间长度一定小于该区间长度,而区间长度len由小到大枚举就保证了[i,j]的每个子区间都被计算过
for(int len=2;len<=n;len++){
for(int i=1;i+len-1<=n;i++){
int j=i+len-1;
for(int k=i;k<j;k++){
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum(i,j);
}
}
}
环形石子合并
#include<iostream>
#include<cstring>
using namespace std;
const int N=210,M=N<<1,INF=0x3f3f3f3f;
int n;
int w[M],s[M];
int f[M][M],g[M][M];
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>w[i],w[i+n]=w[i];
//预处理前缀和
for(int i=1;i<=n<<1;i++){
s[i]=s[i-1]+w[i];
}
memset(f,-0x3f,sizeof f);
memset(g,0x3f,sizeof g);
for(int len=1;len<=n;len++){//区间长度
for(int l=1,r;r=l+len-1,r<n<<1;l++){//左右端点
if(len==1) f[l][l]=g[l][l]=0;
else{
for(int k=1;k+l<=r;k++){//枚举分断点
f[l][r]=max(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
g[l][r]=min(g[l][r],g[l][k]+g[k+1][r]+s[r]-s[l-1]);
}
}
}
}
//目标状态中找方案
int minv=INF,maxv=-INF;
for(int l=1;l<=n;l++){
minv=min(minv,g[l][l+n-1]);
maxv=max(maxv,f[l][l+n-1]);
}
cout<<minv<<endl;
cout<<maxv<<endl;
return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int N=210,M=N<<1,INF=0x3f3f3f3f;
int n;
int w[M],s[M];
int f[M][M],g[M][M];
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>w[i],w[n+i]=w[i];//拆环变直线
memset(f,-0x3f,sizeof f);//最大值
memset(g,0x3f,sizeof g);//最小值
//预处理前缀和
for(int i=1;i<n<<1;i++){
f[i][i]=0;
g[i][i]=0;
s[i]=s[i-1]+w[i];
}
for(int len=2;len<=n;len++){//区间长度
for(int i=1;i+len-1<=n;i++){//枚举左端点
int j=i+len-1;//右端点
for(int k=i;k<j;k++){//枚举断点
f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
g[i][j]=min(g[i][j],g[i][k]+g[k+1][j]+s[j]-s[i-1]);
}
}
}
int minv=INF,maxv=-INF;
for(int i=1;i<=n;i++){
minv=min(minv,g[i][i+n-1]);
maxv=max(maxv,f[i][i+n-1]);
}
cout<<minv<<endl;
cout<<maxv<<endl;
return 0;
}
题型九:线性DP
破损的楼梯
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
const int p=1e9+7;
ll dp[N];
bool broken[N];
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int n,m;
cin>>n>>m;
memset(broken,false,sizeof broken);
for(int i=1;i<m;i++){
int x;
cin>>x;
broken[x]=true;
}
dp[0]=1;//从0->2 一个方案
if(broken[1]=false) dp[1]=1;
for(int i=2;i<=n;i++){
if(broken[i]==true) continue;
dp[i]=(dp[i-1]+dp[i-2])%p;
}
cout<<dp[n]<<endl;
return 0;
}
安全序列
0表示不放 1表示放
dp[i]就在i这个地方标记为1 这个地方的方案数
0000 1000 0100 0010 0001 1001
dp[0]=1 pre[0]=1; 0000 初始化方案数
dp[1]=1 pre[1]=2; 1000 新标记
dp[2]=1 pre[2]=3; 0100 新标记
dp[3]=1 pre[3]=4; 0010 新标记
dp[4]=2 pre[4]=6; 0001 1001新标记 并且出现可以放的间隔数为2的新标记
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e6+10;
const int p=1e9+7;
ll dp[N],pre[N];
int main(){
int n,k;
cin>>n>>k;
dp[0]=pre[0]=1;//一个不放也算一个方案
for(int i=1;i<=n;i++){
if(i-k-1<1) dp[i]=1;//前面没有办法放桶子
else{
dp[i]=pre[i-k-1];//一个是本身放桶子 另一个是前面有桶子后面这个位置再放一个桶子
}
pre[i]=dp[i]+pre[i-1];//以i结尾的所有方案数
}
cout<<pre[n]<<endl;
return 0;
}
可构造序列总数
//从1-k之间找出长度为n的具有倍数关系的区间数
//f[i][j]是只考虑前i个数且第i个数为j的合法方案数
//初始化f[1][i]=1; 序列区间中全为i也是一种情况 初始值
//因为倍数关系 所以转移时 j应该是从它的因子转移来
//f[i][j]+=f[i-1][z] (j mod z==0)
#include<bits/stdc++.h>
using namespace std;
const int N=2020;
typedef long long ll;
const ll p=1e9+7;
int n,k;
int main(){
cin>>k>>n;
vector<vector<ll>> f(n+1,vector<ll>(k+1));//声明二维数组
vector<vector<int>> e(k+1);
for(int i=1;i<=k;i++){ //i=2 j=2 4 6 i是j的因子
for(int j=i;j<=k;j+=i){
e[j].push_back(i);
}
}
for(int i=1;i<=k;i++) f[1][i]=1;
for(int i=2;i<=n;i++){
for(int j=1;j<=k;j++){
for(auto v:e[j]){
f[i][j]=(f[i][j]+f[i-1][v])%p;
}
}
}
ll ans=0;
for(int i=1;i<=k;i++) ans=(ans+f[n][i])%p;//最后总数也要mod
cout<<ans<<endl;
return 0;
}
题型十:二维DP
摆花
#include<bits/stdc++.h>
using namespaces std;
const int N=110;
typedef long long ll;
const ll p=1e6+7;
ll a[N];
ll dp[N][N];
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
dp[0][0]=1;//方案数 一般初始值为1
for(int i=1;i<=n;i++){//n种花
for(int j=0;j<=m;j++)//可以摆放的花盆上
{
for(int k=0;k<=a[i]&&k<=j;k++){//每种花摆放的花束
dp[i][j]=(dp[i][j]+dp[i-1][j-k])%p;//由上一次摆放的花推出
}
}
}
cout<<dp[n][m]<<endl;
return 0;
}
选数异或
//dp[i][j]是到第i个数为止(但不一定选第i个),异或和为j的子序列个数
//对于第i层 转移方式 是选或者不选 两种
//dp[i][j]=dp[i-1][j]+dp[i-1][j^a[i]];
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=1e5+9;
const ll p=998244353;
int a[N],dp[N][70];
int main(){
int n,x;
cin>>n>>x;
for(int i=1;i<=n;i++) cin>>a[i];
dp[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<64;j++){
dp[i][j]=(dp[i-1][j]+dp[i-1][j^a[i]])%p;
}
}
cout<<dp[n][x]<<endl;
return 0;
}
电影放映计划
#include<bits/stdc++.h>
using namespace std;
int M,N;
int main(){
cin>>M>>N;
vector<int> T(N),P(N);
for(int i=0;i<N;i++){
cin>>T[i]>>p[i];
}
int K;
cin>>K;
M+=K;
for(int i=0;i<N;i++){
T[i]+=K;
}
for(int i=0;i<M;i++){
dp[i]=dp[i-1];//离散的dp 这里要进行复制 因为i是离散的
for(int j=0;j<N;j++){
if(i>=T[j]) dp[i]=max(dp[i],dp[i-T[j]]+P[j]);
}
}
cout<<dp[M]<<endl;
return 0;
}