DP蓝桥备赛

动态规划

解题步骤 :

第一步:确定状态

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

  • 14
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值