动态规划——背包

01背包:从右往左(因为只能由上一个物品的状态退出,如果从左往右则前边的保存的已是装了这件物品的值)递推,放不放此物品

完全背包:从左往右递推

多重背包:二进制拆包,或用单调队列优化(应该不考吧看的不明白QAQ)

装满背包:只把f[0]设为0

分组背包:把有依赖关系的方程一组,然后在一个阶段了分别dp不取附件,取一个,取两个(只有主次依赖)

二维费用:再加一维费用的循环

混合背包模板

#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 205
using namespace std;
int n,m,w[maxn],v[maxn],a[maxn],dp[200005];
int main(){
    cin>>n>>m;
    int tw,tv,ta;
    for(int i = 1;i <= n;i++){
        scanf("%d%d%d",&tw,&tv,&ta);
        if(tw > m) n--;
        else{
            w[i] = tw;
            v[i] = tv;
            a[i] = ta;
        }
    }
    for(int i = 1;i <= n;i++){
        if(a[i] == -1){
            for(int j = w[i];j <= m;j++){
                dp[j] = max(dp[j],dp[j-w[i]]+v[i]);
            }
        }
        if(a[i] == 1){
            for(int j = m;j >= w[i];j--){
                dp[j] = max(dp[j],dp[j-w[i]]+v[i]);
            }
        }
        if(a[i] >  1){
            if(a[i] * w[i] >= m){
                for(int j = w[i];j <= m;j++){
                    dp[j] = max(dp[j],dp[j-a[i]*w[i]]+a[i]*v[i]);
                }
                continue;
            }
            int k = 1;
            for(;k<a[i];){
                for(int j = m;j >= w[i]*k;j--){
                    dp[j] = max(dp[j],dp[j-w[i]*k]+v[i]*k);
                }
                a[i] -= k;
                k = k<<1;
            }
            for(int j = m;j >= w[i]*a[i];j--){
                dp[j] = max(dp[j],dp[j-w[i]*a[i]]+v[i]*a[i]);
            }
        }
    }
    cout<<dp[m]<<endl;
    return 0;
}

优先队列优化

#include<iostream>
#include<algorithm>    
int n,m,dp[200005],w[205],v[205],a[205];
using namespace std;
//“多重背包”通用模板
const int MAX_V = 200005;
//v、n、w:当前所处理的这类物品的体积、个数、价值
//V:背包体积, MAX_V:背包的体积上限值
//f[i]:体积为i的背包装前几种物品,能达到的价值上限。
inline void pack(int f[], int V, int v, int n, int w)
{
  if (n == 0 || v == 0) return;
  if (n == 1) {               //01背包
    for (int i = V; i >= v; --i)
      if (f[i] < f[i - v] + w) f[i] = f[i - v] + w;
    return;
  }
  if (n * v >= V - v + 1 || n == -1) {   //完全背包(n >= V / v)
    for (int i = v; i <= V; ++i)
      if (f[i] < f[i - v] + w) f[i] = f[i - v] + w;
    return;    
  }

  int va[MAX_V], vb[MAX_V];   //va/vb: 主/辅助队列
  for (int j = 0; j < v; ++j) {     //多重背包
    int *pb = va, *pe = va - 1;     //pb/pe分别指向队列首/末元素
    int *qb = vb, *qe = vb - 1;     //qb/qe分别指向辅助队列首/末元素  
    for (int k = j, i = 0; k <= V; k += v, ++i) {
      if (pe  == pb + n) {       //若队列大小达到指定值,第一个元素X出队。
        if (*pb == *qb) ++qb;   //若辅助队列第一个元素等于X,该元素也出队。 
        ++pb;
      }
      int tt = f[k] - i * w;
      *++pe = tt;                  //元素X进队
      //删除辅助队列所有小于X的元素,qb到qe单调递减,也可以用二分法
      while (qe >= qb && *qe < tt) --qe;
      *++qe = tt;              //元素X也存放入辅助队列        
      f[k] = *qb + i * w;      //辅助队列首元素恒为指定队列所有元素的最大值
    }
  }
}


int main(){

    cin>>n>>m;
    for(int i = 1;i <= n;i++){
        cin>>w[i]>>v[i]>>a[i];
    }
    for(int i = 1;i <= n;i++){
        pack(dp,m,w[i],a[i],v[i]);
    }
    cout<<dp[m];
    return 0;
}

金明的预算方案

题目的的大体意思就是说,一定钱,可以买主件或主件加附件,求最大钱数,是一个分组背包问题

注意空间优化设零的问题,一开始出错了

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<vector>
#define maxint 987654321
#define maxn 200005
using namespace std;
struct item{
    int w;
    int v;
};
int n,m,vis[maxn],pt[maxn];
int last[maxn],now[maxn],req[maxn],mon[maxn];
vector<item> bag[maxn];
int main(){
    cin>>m>>n;
    int v,p,q,cnt = 0;
    item tmp;
    for(int i = 1;i <= n;i++){
        scanf("%d%d%d",&v,&p,&q);
        if(q == 0){
            pt[++cnt] = i;
            vis[i] = 1;
            req[cnt] = v;
            mon[cnt] = p*v;
        }else{        
            tmp.w = v;
            tmp.v = p*v;
            bag[q].push_back(tmp);
        }

    }
    for(int i = 1;i <= cnt;i++){
        int g = pt[i];
        for(int j = req[i];j <= m;j++){
            now[j] = max(last[j],last[j-req[i]] + mon[i]);
        }
        if(bag[g].size() >= 1){
            for(int j = req[i] + bag[g][0].w;j <= m;j++){
                now[j] = max(now[j],max(last[j],last[j-req[i]-bag[g][0].w] + mon[i] + bag[g][0].v));
            }
        }
        if(bag[g].size() >= 2){
            for(int j = req[i] + bag[g][1].w;j <= m;j++){
                now[j] = max(now[j],max(last[j],last[j-req[i]-bag[g][1].w] + mon[i] + bag[g][1].v));
            }
            for(int j = req[i] + bag[g][1].w + bag[g][0].w;j <= m;j++){
                now[j] = max(now[j],max(last[j],last[j-req[i]-bag[g][1].w-bag[g][0].w] + mon[i] + bag[g][1].v + bag[g][0].v));
            }
        }
        for(int j = 0;j <= m;j++){
            last[j] = now[j];
        }
    }
    cout<<last[m];
    return 0;
}

地鼠游戏

大体意思是说有一些地鼠从0时刻冒出某个时刻消失,每次打地鼠用一秒钟,每个地鼠一些分,求最大分数

数据小可以用背包做,某个时刻打地鼠获得的最大分数由前一秒钟打地鼠的的分数转移而来,注意地鼠消失的时间有大有小,在地鼠消失之前可以打,而每一次更新状态的时候都是由以前的时间转移过来,如果不对地鼠进行任何处理,就会导致时间大的地鼠在打的时候取到的前面的值有可能不是最优值(因为如果在打这个地鼠的状态由后面的地鼠转移而来,那么他现在转移就得不到哪个早就应该被算出来的最优子状态了),这就要求再打的时候保证所有的消失时间比他早的地鼠都已经敲完,综上所述,需要对地鼠进行高度排序(这个地方千千万万个注意,一定要用结构体,否则数据就不对应了,血的教训),codevs上可以这么A

再来数据量大的就要贪心了

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<cmath>
#define maxn 100005
#define ll long long
using namespace std;
struct mouse{
    int value;
    int mtime;
};
int n,dp[maxn],mt,ans;
mouse game[maxn];
bool cmp(mouse a,mouse b){
    return a.mtime < b.mtime;
}
int main(){
    freopen("mouse.in","r",stdin);
    freopen("mouse.out","w",stdout);
    cin>>n;
    for(int i = 1;i <= n;i++) scanf("%d",&game[i].mtime);
    for(int i = 1;i <= n;i++) scanf("%d",&game[i].value);
    sort(game+1,game+1+n,cmp);
    for(int i = 1;i <= n;i++){
            for(int j = game[i].mtime;j >= 1;j--){
                    dp[j] = max(dp[j],dp[j-1] + game[i].value);
                    ans = max(ans,dp[j]);
            }
    }
    cout<<ans<<endl;
    return 0;
}

乌龟棋

大体意思是有一个N个格的棋盘,每个格写着一些分数,一个人手里有四种卡片各若干张,每次可以使用一张卡片往前走1,2,3,4格,获得分数,恰好能到终点,问最多拿多少分

dp设计状态有N,k1,k2,k3,k4五种状态(费用这一维必须加上),然后发现k1,k2,k3,k4可以算出n,所以就只剩四种卡片数量做状态,然后就是四维费用的背包了

注意卡片数量的问题,数组从零开始,所以必须把卡片数量全加一,才能保证一张卡片都不用的状态可以表示

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<map>
#include<vector>
#include<queue>
#include<stack>
#define maxn 505
using namespace std;
int n,m;
int card[maxn],score[maxn],dp[45][45][45][45];
int main(){
    cin>>n>>m;
    for(int i = 0;i < n;i++) cin>>score[i];
    int cmd;
    for(int i = 1;i <= m;i++){
       cin>>cmd;
       card[cmd]++;
    }
    card[1]++;
    card[2]++;
    card[3]++;
    card[4]++;
    for(int i = 1;i <= card[1];i++){
            for(int j = 1;j <= card[2];j++){
                    for(int k = 1;k <= card[3];k++){
                            for(int l = 1;l <= card[4];l++){
                                    dp[i][j][k][l] = score[i*1+j*2+k*3+l*4-10] + max(max(dp[i-1][j][k][l],dp[i][j-1][k][l]),max(dp[i][j][k-1][l],dp[i][j][k][l-1]));
                            }
                    }
            }
    }
    cout<<dp[card[1]][card[2]][card[3]][card[4]];
    return 0;
}

难题:

飞扬的小鸟(滚动数组,复杂实现,棋盘细节处理)No.1

搭建双塔(背包变形,思维转化,同时解决可行性和最优子结构)No.2

原文地址:https://www.cnblogs.com/hyfer/p/4842063.html