背包九讲
所谓背包问题,是针对动态规划的一系列问题,其实后面的八种变种都是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[i−1][j](不选),dp[i−1][j−w[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[j−w[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=1∑a[i]dp[i−1][j−k]然后可以发现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=1∑a[i]dp[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 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;
}