背包入门合辑

背包九讲

所谓背包问题,是针对动态规划的一系列问题,其实后面的八种变种都是01背包的变种。

01背包

思路:这一类背包问题的问题描述如下,有n种宝贝,每个宝贝只有一个,且都有重量和价值,一个背包的容量固定,问最大你能装多少价值的宝贝。
做法:首先考虑用二维数组的转移方程,很好懂,dp[i][j]表示前i种物品花费小于j最多能有多少价值。
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] ( 不 选 ) , d p [ i − 1 ] [ j − w [ i ] ] + v [ i ] ( 选 ) ) dp[i][j] = max(dp[i-1][j](不选), dp[i-1][j-w[i]]+v[i](选)) dp[i][j]=max(dp[i1][j]()dp[i1][jw[i]]+v[i]())而从这个转移方程可以看出来,其实每个物品都是由它的上一个物品转移而来,所以可以将这个二维数组降到一维。
d p [ j ] = m a x ( d p [ j ] ( 不 选 ) , d p [ j − w [ i ] ] + v [ i ] ( 选 ) ) dp[j] = max(dp[j](不选),dp[j-w[i]]+v[i](选)) dp[j]=max(dp[j](),dp[jw[i]]+v[i]())每个dp都是记录了前一个的状态,在转移时要注意第二重循环倒序,因为如果正序,会造成重复访问的情况,这样就演变成第二类背包——完全背包。
代码:

#include<bits/stdc++.h>
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define PI acos(-1)
#define eps 1e-5
using namespace std;
typedef long long LL;
typedef pair<LL, int> pli;
typedef pair<int, int> pii;
typedef pair<LL, LL> pll;
typedef pair<double, double> pdd;
typedef map<char, int> mci;
typedef map<string, int> msi;
template<class T>
void read(T &res) {
    
    
  int f = 1; res = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    
     if(c == '-') f = -1; c = getchar(); }
  while(c >= '0' && c <= '9') {
    
     res = res * 10 + c - '0'; c = getchar(); }
  res *= f;
}
const int N = 1e5+5;
const int M = 1e4+4;
int dp[N], w[M], v[M];
int main() {
    
    
  int m, n;
  scanf("%d %d", &m, &n);
  for(int i = 1; i <= n; ++i) scanf("%d %d", &w[i], &v[i]);
  for(int i = 1; i <= n; ++i) {
    
    
    for(int j = m; j >= w[i]; --j) {
    
    
      dp[j] = max(dp[j], dp[j-w[i]] + v[i]);
    }
  }
  printf("%d\n", dp[m]);
  return 0;
}

完全背包

思路:这一类背包的问题描述如下,有n种宝贝,每个宝贝有无限个,且都有重量和价值,一个背包的容量固定,问最大你能装多少价值的宝贝。
做法如01背包所述,就是第二重循环改成正序。

代码:

#include<bits/stdc++.h>
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define PI acos(-1)
#define eps 1e-5
using namespace std;
typedef long long LL;
typedef pair<LL, int> pli;
typedef pair<int, int> pii;
typedef pair<LL, LL> pll;
typedef pair<double, double> pdd;
typedef map<char, int> mci;
typedef map<string, int> msi;
template<class T>
void read(T &res) {
    
    
  int f = 1; res = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    
     if(c == '-') f = -1; c = getchar(); }
  while(c >= '0' && c <= '9') {
    
     res = res * 10 + c - '0'; c = getchar(); }
  res *= f;
}
const int N = 1e5+5;
const int M = 1e4+4;
int dp[N], w[M], v[M];
int main() {
    
    
  int m, n;
  scanf("%d %d", &m, &n);
  for(int i = 1; i <= n; ++i) scanf("%d %d", &w[i], &v[i]);
  for(int i = 1; i <= n; ++i) {
    
    
    for(int j = w[i]; j <= m; ++j) {
    
    
      dp[j] = max(dp[j], dp[j-w[i]] + v[i]);
    }
  }
  printf("%d\n", dp[m]);
  return 0;
}

多重背包

思路:这一类背包的问题描述如下,有n种宝贝,每个宝贝有有限个,且都有重量和价值,一个背包的容量固定,问最大你能装多少价值的宝贝。
做法:也是一个01背包的变种,其中需要用到一个二进制优化,听起来很玄幻,实际上就是把这有限个物品拆分成若干二进制次方的个数,每个十进制数都有唯一一个确定的二进制与其对应,用这样的方式可以优化时间复杂度到log级别, 也就是说将O(n3)优化到O(n2logn),若这样的时间复杂度还是卡不过去,就得用队列再进行优化了,大部分题二进制优化即可。

代码:

#include<bits/stdc++.h>
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define PI acos(-1)
#define eps 1e-5
using namespace std;
typedef long long LL;
typedef pair<LL, int> pli;
typedef pair<int, int> pii;
typedef pair<LL, LL> pll;
typedef pair<double, double> pdd;
typedef map<char, int> mci;
typedef map<string, int> msi;
template<class T>
void read(T &res) {
    
    
  int f = 1; res = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    
     if(c == '-') f = -1; c = getchar(); }
  while(c >= '0' && c <= '9') {
    
     res = res * 10 + c - '0'; c = getchar(); }
  res *= f;
}
const int N = 2010;
int dp[N];
struct xx {
    
    
  int w, v;
};
vector<xx> goods;
int main() {
    
    
  int m, n, s;
  read(n); read(m); 
  for(int i = 0, v, w, s; i < n; ++i) {
    
    
    read(w); read(v); read(s);
    for(int j = 1; j <= s; j *= 2) {
    
    
      s -= j;
      goods.push_back(xx{
    
    w*j, v*j});
    }
    if(s > 0) goods.push_back(xx{
    
    w*s, v*s});
  }
  for(auto good : goods) {
    
    
    for(int j = m; j >= good.w; --j) {
    
    
      dp[j] = max(dp[j], dp[j-good.w] + good.v);
    }
  }
  printf("%d\n", dp[m]);
  return 0;
}

混合背包

思路:这一类的背包问题描述如下,有n个宝贝,m的容量,每个宝贝都有价值和重量,并且属于一个背包种类,总共是3种,对于第一种背包,至少得选一个宝贝,对于第二种,至多选一个宝贝,对于第三种,不限制(当然这三种也可以是其他的多重背包,完全背包之类),问最多能获得多少价值。
做法:这样的背包也是以上背包的变形,只要搞懂每一种背包若单独拎出来要怎么求就行,所以对于每个背包,每一个种类单独考虑,在第二重循环分类讨论即可。

代码:

#include<bits/stdc++.h>
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define PI acos(-1)
#define eps 1e-5
using namespace std;
typedef long long LL;
typedef pair<LL, int> pli;
typedef pair<int, int> pii;
typedef pair<LL, LL> pll;
typedef pair<double, double> pdd;
typedef map<char, int> mci;
typedef map<string, int> msi;
template<class T>
void read(T &res) {
    
    
  int f = 1; res = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    
     if(c == '-') f = -1; c = getchar(); }
  while(c >= '0' && c <= '9') {
    
     res = res * 10 + c - '0'; c = getchar(); }
  res *= f;
}
const int INF = 0x3f3f3f3f;
const int N = 110;
const int M = 40;
struct xx {
    
    
  int w, v;
};
int v[N], w[N], dp[N][N];
int main() {
    
    
  int n, T, m, s;
  while(~scanf("%d %d", &n, &T)) {
    
    
    memset(dp, 0, sizeof dp);
    for(int i = 1; i <= n; ++i) {
    
    
      scanf("%d %d", &m, &s);
      for(int k = 1; k <= m; ++k) {
    
    
        scanf("%d %d", &w[k], &v[k]);
      }
      if(s == 0) {
    
     //至少完成一件的01背包写法
        for(int j = 0; j <= T; ++j) dp[i][j] = -INF;
        for(int k = 1; k <= m; ++k) {
    
    
          for(int j = T; j >= w[k]; --j) {
    
    
            dp[i][j] = max(dp[i][j], dp[i][j-w[k]] + v[k]);
            dp[i][j] = max(dp[i][j], dp[i-1][j-w[k]] + v[k]);
          }
        }
      } else if(s == 1) {
    
     //至多完成一件的01背包写法
        for(int j = 0; j <= T; ++j) dp[i][j] = dp[i-1][j];
        for(int k = 1; k <= m; ++k) {
    
    
          for(int j = T; j >= w[k]; --j) {
    
    
            dp[i][j] = max(dp[i][j], dp[i-1][j-w[k]] + v[k]);
          }
        }
      } else if(s == 2) {
    
      //不限制的01背包写法
        for(int j = 0; j <= T; ++j) dp[i][j] = dp[i-1][j];
        for(int k = 1; k <= m; ++k) {
    
    
          for(int j = T; j >= w[k]; --j) {
    
    
            dp[i][j] = max(dp[i][j], dp[i][j-w[k]] + v[k]);
          }
        }
      }
    }
    printf("%d\n", max(dp[n][T], -1));
  }
  return 0;
}

分组背包

思路:这一类背包问题的问题描述如下,有n个宝贝,每个宝贝都有重量和价值,且被分组,对于每一组背包只能选一个,一个背包的容量固定,问最大你能装多少价值的宝贝。

做法:先把每个物品按照分组安排好,我们先将一个组的物品形似成01背包中的一个物品,对于每一个组,就有选与不选两种方案,如果选,就在第三层循环里,再对这个组的物品遍历,更新物品的最大值。

代码:

#include<bits/stdc++.h>
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define PI acos(-1)
#define eps 1e-5
using namespace std;
typedef long long LL;
typedef pair<LL, int> pli;
typedef pair<int, int> pii;
typedef pair<LL, LL> pll;
typedef pair<double, double> pdd;
typedef map<char, int> mci;
typedef map<string, int> msi;
template<class T>
void read(T &res) {
    
    
  int f = 1; res = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    
     if(c == '-') f = -1; c = getchar(); }
  while(c >= '0' && c <= '9') {
    
     res = res * 10 + c - '0'; c = getchar(); }
  res *= f;
}
const int N = 1003;
int dp[N];
vector<pii> ve[N];
int main() {
    
    
  int n, m;
  read(m); read(n);
  for(int i = 0, a, b, c; i < n; ++i) {
    
    
    read(a); read(b); read(c);
    ve[c].push_back(make_pair(a, b));
  }
  for(int c = 0, len; c <= 100; ++c) {
    
    
    len = ve[c].size();
    if(len == 0) continue;
    for(int i = m; i >= 0; --i) {
    
    
      for(int j = 0; j < len; ++j) {
    
    
        if(i >= ve[c][j].first)
          dp[i] = max(dp[i], dp[i-ve[c][j].first] + ve[c][j].second);
      }
    }
  }
  printf("%d\n", dp[m]);
  return 0;
}

有依赖的背包

思路:这一类背包问题的问题描述如下,有n个宝贝,宝贝分为主件和附件两种,每个附件只依附于一个主件,主件和附件都有价值和重量,只有在选择了主件的情况下才能选择附件,问最大能取多少价值。

做法:针对每个主件,在保证主件重量有效的前提下对附件进行01背包,要用到h数组,是对每个主件的附件01背包保存状态的数组,最后用h数组更新的dp数组是最终的状态。

代码:

#include<bits/stdc++.h>
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define PI acos(-1)
#define eps 1e-5
using namespace std;
typedef long long LL;
typedef pair<LL, int> pli;
typedef pair<int, int> pii;
typedef pair<LL, LL> pll;
typedef pair<double, double> pdd;
typedef map<char, int> mci;
typedef map<string, int> msi;
template<class T>
void read(T &res) {
    
    
  int f = 1; res = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    
     if(c == '-') f = -1; c = getchar(); }
  while(c >= '0' && c <= '9') {
    
     res = res * 10 + c - '0'; c = getchar(); }
  res *= f;
}
const int N = 5e5+5;
struct xx {
    
    
  int w, v, group;
}a[1004];
int dp[N], h[N];
int main() {
    
    
  int n, m;
  read(m); read(n);
  for(int i = 1; i <= n; ++i) {
    
    
    read(a[i].w); read(a[i].v); read(a[i].group);
    a[i].v *= a[i].w;
  }
  for(int i = 1; i <= n; ++i) {
    
    
    if(a[i].group) continue;
    //对h数组用dp数组初始化
    for(int j = 1; j < a[i].w; ++j) h[j] = 0;
    for(int j = a[i].w; j <= m; ++j) h[j] = dp[j-a[i].w] + a[i].v;
    //对当前主件的附件进行01背包,更新h数组
    for(int j = 1; j <= n; ++j) {
    
    
      if(a[j].group == i) {
    
    
        for(int k = m; k >= a[i].w + a[j].w; --k)
          h[k] = max(h[k], h[k-a[j].w] + a[j].v);
      }
    }
    //用dp数组记录下h数组中得到的最大价值
    for(int j = a[i].w; j <= m; ++j) dp[j] = max(dp[j], h[j]);
  }
  printf("%d\n", dp[m]);
  return 0;
}

第K优解

思路:这一类背包问题的问题描述如下,有n种宝贝,每个宝贝只有一个,且都有重量和价值,一个背包的容量固定,问获得的总价值第K大是多少。

做法:dp[i][j]代表费用为i时第j优的总价值,所以枚举每个j,算到第k大,要注意去除重复。

代码:

#include<bits/stdc++.h>
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define PI acos(-1)
#define eps 1e-5
using namespace std;
typedef long long LL;
typedef pair<LL, int> pli;
typedef pair<int, int> pii;
typedef pair<LL, LL> pll;
typedef pair<double, double> pdd;
typedef map<char, int> mci;
typedef map<string, int> msi;
template<class T>
void read(T &res) {
    
    
  int f = 1; res = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    
     if(c == '-') f = -1; c = getchar(); }
  while(c >= '0' && c <= '9') {
    
     res = res * 10 + c - '0'; c = getchar(); }
  res *= f;
}
const int INF = 0x3f3f3f3f;
const int N = 110;
const int M = 40;
int w[N], v[N];
int dp[1010][M], a[M], b[M];
int main() {
    
    
  int t; scanf("%d", &t);
  int n, m, k;
  while(t--) {
    
    
    memset(dp, 0, sizeof dp);
    for(int i = 0; i < 40; ++i) a[i] = b[i] = 0;
    scanf("%d %d %d", &n, &m, &k);
    for(int i = 1; i <= n; ++i) scanf("%d", &v[i]);
    for(int i = 1; i <= n; ++i) scanf("%d", &w[i]);
    for(int i = 1, j, p, x, y, z; i <= n; ++i) {
    
    
      for(j = m; j >= w[i]; --j) {
    
    
        for(p = 1; p <= k; ++p) {
    
    
          a[p] = dp[j-w[i]][p] + v[i];  //选
          b[p] = dp[j][p];  //不选
        }
        //把a、b数组的结果更新到dp数组中,注意去重
        a[p] = b[p] = -1; x = y = z = 1;
        while(z <= k && (a[x] != -1 || b[y] != -1)) {
    
    
          if(a[x] > b[y]) dp[j][z] = a[x], ++x;
          else dp[j][z] = b[y], ++y;
          if(dp[j][z] != dp[j][z-1]) ++z;
        }
      }
    }
    printf("%d\n", dp[m][k]);
  }
  return 0;
}

系列水题

P1095 守望者的逃离

题意:题意很好理解,就是一个逃亡者需要在规定时间内逃出大岛,其中能采取两种方式,问能否顺利逃跑,如果能要求最短时间,如果不能,求的是最长能跑多源。

做法:一开始啊,以为是一套贪心,结果if else判断到去世,并且去世后也只拿了90分。其实正解很短,就是用两次01背包,分别对两种方式进行两次01背包。

代码(90分):

#include<bits/stdc++.h>
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define PI acos(-1)
#define eps 1e-5
#define endl '\n'
using namespace std;
typedef long long LL;
typedef pair<LL, int> pli;
typedef pair<int, int> pii;
typedef pair<LL, LL> pll;
typedef pair<double, double> pdd;
typedef map<char, int> mci;
typedef map<string, int> msi;
template<class T>
void read(T &res) {
    
    
  int f = 1; res = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    
     if(c == '-') f = -1; c = getchar(); }
  while(c >= '0' && c <= '9') {
    
     res = res * 10 + c - '0'; c = getchar(); }
  res *= f;
}
const int N = 20;
int main() {
    
    
  LL m, s, t;
  cin >> m >> s >> t;
  if(m / 10 * 60 >= s) {
    
    
    if(t * 60 >= s) {
    
    
      puts("Yes");
      cout << ceil(1.0 * s / 60) << endl;
    } else {
    
    
      puts("No");
      cout << t * 60 << endl;
    }
  } else {
    
    
    if(t < m / 10) {
    
    
      puts("No");
      cout << t * 60 << endl;
    } else {
    
    
      LL tt = t, ss = s, mid, t1, t2;
      s -= m / 10 * 60;
      t -= m / 10;
      m = m % 10;
      while(t >= 0) {
    
    
        //cout << m << " " << s << " " << t << endl;
        mid = ceil(1.0 * (10 - m) / 4) + 1;
        t1 = mid;
        t2 = ceil(1.0*s / 17);
        if(t >= mid) {
    
    
          if(s > 60) {
    
    
            s -= 60;
            t -= mid;
            m = (mid - 1) * 4 + m - 10;
          } else {
    
    
            puts("Yes");
            cout << tt - (t - min(t1, t2)) << endl;
            break;
          }
        } else {
    
    
          if(t >= t2) {
    
    
            puts("Yes");
            cout << tt - (t - t2) << endl;
          }
          else {
    
    
            puts("No");
            cout << ss - (s - t * 17) << endl;
          }
          break;
        }
      }
    }
  }
  return 0;
}

代码(100分):

#include<bits/stdc++.h>
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define PI acos(-1)
#define eps 1e-5
#define endl '\n'
using namespace std;
typedef long long LL;
typedef pair<LL, int> pli;
typedef pair<int, int> pii;
typedef pair<LL, LL> pll;
typedef pair<double, double> pdd;
typedef map<char, int> mci;
typedef map<string, int> msi;
template<class T>
void read(T &res) {
    
    
  int f = 1; res = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    
     if(c == '-') f = -1; c = getchar(); }
  while(c >= '0' && c <= '9') {
    
     res = res * 10 + c - '0'; c = getchar(); }
  res *= f;
}
const int N = 3e5+5;
int dp[N];
int main() {
    
    
  LL m, s, t;
  cin >> m >> s >> t;
  for(int i = 1; i <= t; ++i) {
    
    
    if(m >= 10) dp[i] = dp[i-1] + 60, m -= 10;
    else dp[i] = dp[i-1], m += 4;
  }
  for(int i = 1; i <= t; ++i) {
    
    
    dp[i] = max(dp[i], dp[i-1] + 17);
    if(dp[i] >= s) {
    
    
      printf("Yes\n%d", i);
      return 0;
    }
  }
  printf("No\n%d", dp[t]);
  return 0;
}

HDU2159 FATE

题意:需要n点经验,只有m的耐久度,有k种怪兽,最多不能再打s只怪兽,问最后剩的最大耐久度是多少。

做法:用dp[i]表示耐久度为i时最大的经验值。一开始的想法是错误的,想用完全背包的未优化代码,还是对完全背包不理解呀,这样的写法最能求到一个种类的怪兽数量不超过s,不能控制全部种类的数量,所以正解用了num数组来表示耐久度为i时用到怪兽的数量。

WA代码:

#include<bits/stdc++.h>
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define PI acos(-1)
#define eps 1e-5
#define endl '\n'
using namespace std;
typedef long long LL;
typedef pair<LL, int> pli;
typedef pair<int, int> pii;
typedef pair<LL, LL> pll;
typedef pair<double, double> pdd;
typedef map<char, int> mci;
typedef map<string, int> msi;
template<class T>
void read(T &res) {
    
    
  int f = 1; res = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    
     if(c == '-') f = -1; c = getchar(); }
  while(c >= '0' && c <= '9') {
    
     res = res * 10 + c - '0'; c = getchar(); }
  res *= f;
}
const int INF = 0x3f3f3f3f;
const int N = 2e4+4;
const int M = 103;
int dp[M], v[M], w[M];
int main() {
    
    
  int n, m, k, s, res;
  while(~scanf("%d %d %d %d", &n, &m, &k, &s)) {
    
    
    for(int i = 1; i <= k; ++i) scanf("%d %d", &v[i], &w[i]);
    for(int i = 0; i <= m; ++i) dp[i] = 0;
    for(int i = 1; i <= k; ++i) {
    
    
      for(int j = m; j >= w[i]; --j) {
    
    
        for(int t = 0; t * w[i] <= j && t <= s; ++t) {
    
    
          dp[j] = max(dp[j], dp[j - t*w[i]] + t*v[i]);
        }
      }
    }
    res = -1;
    for(int i = 0; i <= m; ++i) {
    
    
      if(dp[i] >= n) {
    
    
        res = m - i; break;
      }
    }
    printf("%d\n", res);
  }
  return 0;
}

AC代码:

#include<bits/stdc++.h>
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define PI acos(-1)
#define eps 1e-5
#define endl '\n'
using namespace std;
typedef long long LL;
typedef pair<LL, int> pli;
typedef pair<int, int> pii;
typedef pair<LL, LL> pll;
typedef pair<double, double> pdd;
typedef map<char, int> mci;
typedef map<string, int> msi;
template<class T>
void read(T &res) {
    
    
  int f = 1; res = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    
     if(c == '-') f = -1; c = getchar(); }
  while(c >= '0' && c <= '9') {
    
     res = res * 10 + c - '0'; c = getchar(); }
  res *= f;
}
const int INF = 0x3f3f3f3f;
const int N = 2e4+4;
const int M = 103;
int dp[M], v[M], w[M], num[M];
int main() {
    
    
  int n, m, k, s, res;
  while(~scanf("%d %d %d %d", &n, &m, &k, &s)) {
    
    
    for(int i = 1; i <= k; ++i) scanf("%d %d", &v[i], &w[i]);
    for(int i = 0; i <= m; ++i) dp[i] = 0, num[i] = 0;
    for(int i = 1; i <= k; ++i) {
    
    
      for(int j = w[i]; j <= m; j += w[i]) {
    
    
        if(dp[j] < dp[j-w[i]] + v[i]) {
    
    
          dp[j] = dp[j-w[i]] + v[i];
          num[j] = max(num[j], num[j-w[i]] + 1);
        }
      }
    }
    res = -1;
    for(int i = 0; i <= m; ++i) {
    
    
      if(dp[i] >= n && num[i] <= s) {
    
    
        res = m - i; break;
      }
    }
    printf("%d\n", res);
  }
  return 0;
}

HDU1561 The more, The Better

题意:有n个城堡,只能攻击m个城堡,在攻击第i个城堡前必须先攻克第a个城堡。问价值最大是多少?

做法:树形dp解法,dp[i][j]表示以i为根节点的儿子们攻克j个能获得的最大价值是多少。遍历一遍树,回溯时更新父亲能获得的最大价值。

代码:

#include<bits/stdc++.h>
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define PI acos(-1)
#define eps 1e-5
using namespace std;
typedef long long LL;
typedef pair<LL, int> pli;
typedef pair<int, int> pii;
typedef pair<LL, LL> pll;
typedef pair<double, double> pdd;
typedef map<char, int> mci;
typedef map<string, int> msi;
template<class T>
void read(T &res) {
    
    
  int f = 1; res = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    
     if(c == '-') f = -1; c = getchar(); }
  while(c >= '0' && c <= '9') {
    
     res = res * 10 + c - '0'; c = getchar(); }
  res *= f;
}
const int N = 205;
const int M = 1e4+4;
struct xx {
    
    
  int u, v, next;
}edge[N];
int tot, head[N];
void Add(int u, int v) {
    
    
  edge[++tot] = xx{
    
    u, v, head[u]};
  head[u] = tot;
}
int val[N], dp[N][N];
void dfs(int u, int m) {
    
    
  dp[u][1] = val[u];
  int v;
  for(int i = head[u]; i; i = edge[i].next) {
    
    
    v = edge[i].v;
    dfs(v, m);
    for(int j = m; j >= 2; --j) {
    
    
      for(int k = 0; k < j; ++k) {
    
    
        dp[u][j] = max(dp[u][j], dp[u][j-k] + dp[v][k]);
      }
    }
  }
}
int main() {
    
    
  int n, m;
  while(scanf("%d%d", &n, &m), n||m) {
    
    
    tot = 0;
    memset(head, 0, sizeof head);
    for(int i = 1, a; i <= n; ++i) {
    
    
      scanf("%d%d", &a, val + i);
      Add(a, i);
    }
    memset(dp, 0, sizeof dp);
    dfs(0, m+1);
    printf("%d\n", dp[0][m+1]);
  }
  return 0;
}

HDU1011 Starship Troopers

题意:有n个房间(树结构,入口是根节点),m个士兵,每个士兵可以击败20个虫子,但不能回头,每个房间有一定的价值,求最多能获得多少价值。

做法:树形dp写法,dp[i][j]表示在第i个房间消耗j个士兵能获得的最大价值,由儿子推到父亲的n^3做法比较好写。

代码:

#include<bits/stdc++.h>
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define PI acos(-1)
#define eps 1e-5
using namespace std;
typedef long long LL;
typedef pair<LL, int> pli;
typedef pair<int, int> pii;
typedef pair<LL, LL> pll;
typedef pair<double, double> pdd;
typedef map<char, int> mci;
typedef map<string, int> msi;
template<class T>
void read(T &res) {
    
    
  int f = 1; res = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    
     if(c == '-') f = -1; c = getchar(); }
  while(c >= '0' && c <= '9') {
    
     res = res * 10 + c - '0'; c = getchar(); }
  res *= f;
}
const int INF = 0x3f3f3f3f;
const int N = 205;
const int M = 1e4+4;
struct xx {
    
    
  int v, next;
}edge[N];
int tot, head[N];
void Add(int u, int v) {
    
    
  edge[++tot] = xx{
    
    v, head[u]};
  head[u] = tot;
}
int n, m;
int wt[N], val[N], vis[N], dp[N][N];
void dfs(int u) {
    
    
  vis[u] = 1;
  int v;
  for(int i = wt[u]; i <= m; ++i) dp[u][i] = val[u];
  for(int i = head[u]; i; i = edge[i].next) {
    
    
    v = edge[i].v;
    if(vis[v]) continue;
    dfs(v);
    for(int j = m; j >= wt[u]; --j) {
    
    
      for(int k = 1; k <= j-wt[u]; ++k) {
    
    
        dp[u][j] = max(dp[u][j], dp[u][j-k] + dp[v][k]);
      }
    }
  }
}
int main() {
    
    
  while(~scanf("%d %d", &n, &m)) {
    
    
    if(n == -1 && m == -1) break;
    memset(head, 0, sizeof head);
    memset(dp, 0, sizeof dp);
    memset(vis, 0, sizeof vis);
    tot = 0;
    for(int i = 1; i <= n; ++i) {
    
    
      scanf("%d %d", &wt[i], &val[i]);
      wt[i] = ceil(1.0*wt[i] / 20);
    }
    for(int i = 1, u, v; i < n; ++i) {
    
    
      scanf("%d %d", &u, &v);
      Add(u, v); Add(v, u);
    }
    if(!m) {
    
    
      puts("0"); continue;
    }
    dfs(1);
    printf("%d\n", dp[1][m]);
  }
  return 0;
}

HDU3033 I love sneakers!

题意:有很多种不同品牌的球鞋,要求每个品牌至少买一个产品,每个产品只能买一次,问能获得的最大价值。

做法:如果是每个产品最多只能买一次,那就是一个典型的分组背包问题,但是至少买一次,就懵了很久,嗯…正解如下:
首先把dp数组初始化为-1,表示不可到达的状态。dp[i][j]代表前i组花费j元钱能买到的最大价值。然后dp[i][j]可以由两种方式转移而来,一个是dp[i][j-w[i]]一个是dp[i-1][j-w[i]],要在它们不是-1的情况下转移过来。

代码:

#include<bits/stdc++.h>
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define PI acos(-1)
#define eps 1e-5
using namespace std;
typedef long long LL;
typedef pair<LL, int> pli;
typedef pair<int, int> pii;
typedef pair<LL, LL> pll;
typedef pair<double, double> pdd;
typedef map<char, int> mci;
typedef map<string, int> msi;
template<class T>
void read(T &res) {
    
    
  int f = 1; res = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    
     if(c == '-') f = -1; c = getchar(); }
  while(c >= '0' && c <= '9') {
    
     res = res * 10 + c - '0'; c = getchar(); }
  res *= f;
}
const int INF = 0x3f3f3f3f;
const int N = 110;
const int M = 40;
int dp[N][10010];
int w[N], v[N], a[N];
int main() {
    
    
  int n, m, k, f;
  while(~scanf("%d %d %d", &n, &m, &k)) {
    
    
    memset(dp, -1, sizeof dp);
    for(int i = 0; i <= m; ++i) dp[0][i] = 0;
    for(int i = 0; i < n; ++i) {
    
    
      scanf("%d %d %d", &a[i], &w[i], &v[i]);
    }
    for(int c = 1; c <= k; ++c) {
    
    
      for(int i = 0; i < n; ++i) {
    
    
        if(a[i] == c) {
    
    
          for(int j = m; j >= w[i]; --j) {
    
    
            if(dp[c][j-w[i]] != -1)
              dp[c][j] = max(dp[c][j], dp[c][j-w[i]] + v[i]);
            if(dp[c-1][j-w[i]] != -1)
              dp[c][j] = max(dp[c][j], dp[c-1][j-w[i]] + v[i]);
          }
        }
      }
    }
    if(dp[k][m] != -1) printf("%d\n", dp[k][m]);
    else puts("Impossible");
  }
  return 0;
}

HDU3535 AreYouBusy

题意:有三种选择,至少完成一件,至多完成一件,不限制。

做法:混合背包做法,分别对三种进行混合背包,关键是这三个背包的实现。

代码:

#include<bits/stdc++.h>
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define PI acos(-1)
#define eps 1e-5
using namespace std;
typedef long long LL;
typedef pair<LL, int> pli;
typedef pair<int, int> pii;
typedef pair<LL, LL> pll;
typedef pair<double, double> pdd;
typedef map<char, int> mci;
typedef map<string, int> msi;
template<class T>
void read(T &res) {
    
    
  int f = 1; res = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    
     if(c == '-') f = -1; c = getchar(); }
  while(c >= '0' && c <= '9') {
    
     res = res * 10 + c - '0'; c = getchar(); }
  res *= f;
}
const int INF = 0x3f3f3f3f;
const int N = 110;
const int M = 40;
struct xx {
    
    
  int w, v;
};
int v[N], w[N], dp[N][N];
int main() {
    
    
  int n, T, m, s;
  while(~scanf("%d %d", &n, &T)) {
    
    
    memset(dp, 0, sizeof dp);
    for(int i = 1; i <= n; ++i) {
    
    
      scanf("%d %d", &m, &s);
      for(int k = 1; k <= m; ++k) {
    
    
        scanf("%d %d", &w[k], &v[k]);
      }
      if(s == 0) {
    
     //至少完成一件的01背包写法
        for(int j = 0; j <= T; ++j) dp[i][j] = -INF;
        for(int k = 1; k <= m; ++k) {
    
    
          for(int j = T; j >= w[k]; --j) {
    
    
            dp[i][j] = max(dp[i][j], dp[i][j-w[k]] + v[k]);
            dp[i][j] = max(dp[i][j], dp[i-1][j-w[k]] + v[k]);
          }
        }
      } else if(s == 1) {
    
     //至多完成一件的01背包写法
        for(int j = 0; j <= T; ++j) dp[i][j] = dp[i-1][j];
        for(int k = 1; k <= m; ++k) {
    
    
          for(int j = T; j >= w[k]; --j) {
    
    
            dp[i][j] = max(dp[i][j], dp[i-1][j-w[k]] + v[k]);
          }
        }
      } else if(s == 2) {
    
      //不限制的01背包写法
        for(int j = 0; j <= T; ++j) dp[i][j] = dp[i-1][j];
        for(int k = 1; k <= m; ++k) {
    
    
          for(int j = T; j >= w[k]; --j) {
    
    
            dp[i][j] = max(dp[i][j], dp[i][j-w[k]] + v[k]);
          }
        }
      }
    }
    printf("%d\n", max(dp[n][T], -1));
  }
  return 0;
}

P1077 摆花

题意:有n种花,要放m盆花,每种花不能超过a[i]盆,然后同种花一起放,并且从小到大摆放。

做法:这一题很有意思,乍一眼看感觉挺难的,翻一翻题解,很好理解。一个常规的动态规划,dp[i][j]表示为前i种花摆放j盆的方案,所以有转移方程 d p [ i ] [ j ] = ∑ k = 1 a [ i ] d p [ i − 1 ] [ j − k ] dp[i][j]=\sum_{k=1}^{a[i]} dp[i-1][j-k] dp[i][j]=k=1a[i]dp[i1][jk]然后可以发现dp[i][j]总是由dp[i-1][~]转移过来的,所以可以压缩到一维。 d p [ j ] = ∑ k = 1 a [ i ] d p [ j − k ] dp[j]=\sum_{k=1}^{a[i]} dp[j-k] dp[j]=k=1a[i]dp[jk]

二维代码:

#include<bits/stdc++.h>
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define PI acos(-1)
#define eps 1e-5
using namespace std;
typedef long long LL;
typedef pair<LL, int> pli;
typedef pair<int, int> pii;
typedef pair<LL, LL> pll;
typedef pair<double, double> pdd;
typedef map<char, int> mci;
typedef map<string, int> msi;
template<class T>
void read(T &res) {
    
    
  int f = 1; res = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    
     if(c == '-') f = -1; c = getchar(); }
  while(c >= '0' && c <= '9') {
    
     res = res * 10 + c - '0'; c = getchar(); }
  res *= f;
}
const int INF = 0x3f3f3f3f;
const int N = 110;
const int Mod = 1000007;
int a[N], f[N][N];
int main() {
    
    
  int n, m;
  scanf("%d %d", &n, &m);
  for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
  f[0][0] = 1;
  for(int i = 1; i <= n; ++i) {
    
    
    for(int j = 0; j <= m; ++j) {
    
    
      for(int k = 0; k <= min(a[i], j); ++k) {
    
    
        f[i][j] = (f[i][j] + f[i-1][j-k]) % Mod;
      }
    }
  }
  printf("%d\n", f[n][m]);
  return 0;
}

一维代码:

#include<bits/stdc++.h>
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define PI acos(-1)
#define eps 1e-5
using namespace std;
typedef long long LL;
typedef pair<LL, int> pli;
typedef pair<int, int> pii;
typedef pair<LL, LL> pll;
typedef pair<double, double> pdd;
typedef map<char, int> mci;
typedef map<string, int> msi;
template<class T>
void read(T &res) {
    
    
  int f = 1; res = 0;
  char c = getchar();
  while(c < '0' || c > '9') {
    
     if(c == '-') f = -1; c = getchar(); }
  while(c >= '0' && c <= '9') {
    
     res = res * 10 + c - '0'; c = getchar(); }
  res *= f;
}
const int INF = 0x3f3f3f3f;
const int N = 110;
const int Mod = 1000007;
int a[N], f[N];
int main() {
    
    
  int n, m;
  scanf("%d %d", &n, &m);
  for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
  f[0] = 1;
  for(int i = 1; i <= n; ++i) {
    
    
    for(int j = m; j >= 0; --j) {
    
    
      for(int k = 1; k <= min(a[i], j); ++k) {
    
    
        f[j] = (f[j] + f[j-k]) % Mod;
      }
    }
  }
  printf("%d\n", f[m]);
  return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_43408978/article/details/102899156
今日推荐