只挑了一部分我觉得有点意义的题目
1.HDU-2159 FATE:http://acm.hdu.edu.cn/showproblem.php?pid=2159
题意:刷怪升级,有k种怪,刷一只怪消耗bi忍耐值,获得ai经验, 升级需要n点经验值,还有m点忍耐值,并且自己最多刷s只 怪,问是否能升级,以及如果能,最多还能剩多少忍耐值。(0<n,m,k,s <100, 0 < a, b < 20)
题解: 二维花费的完全背包,最简单的dp[i][j][k]表示: 到第i只怪为止,用j点忍耐,k只怪所获得的最大经验值.
则有转移方程:dp[i][j][k] = max(dp[i-1][j][k], dp[i][j-b[i]][k-1]+a[i]) 表示对于第i只怪有两种策略:1.不刷,2.至少刷一只.
然后发现可以取消第一维.注意第二种策略是包含了第i只怪对自身的影响,所以枚举j,k应该从小到大枚举.找答案时,从小到大 枚举j,只要存在一个dp[j][k] >= n && k <= s的,直接跳出.
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e2+10;
#define fi first
#define se second
int dp[maxn][maxn];
int w[maxn], val[maxn];
int main() {
std::ios::sync_with_stdio(false);
int n, m, k, s;
while(cin >> n >> m >> k >> s) {
for(int i = 1; i <= k; i++)
cin >> val[i] >> w[i];
memset(dp, 0, sizeof dp);
bool flag = false; int ans = 200;
for(int i = 1; i <= k; i++) {
for(int j = w[i]; j <= m; j++) {
for(int t = 1; t <= s; t++) {
dp[j][t] = max(dp[j][t], dp[j-w[i]][t-1]+val[i]);
if(dp[j][t] >= n) ans = min(ans, j);
}
}
}
if(ans == 200) cout << "-1\n";
else cout << m-ans << "\n";
}
return 0;
}
2.HDU-2955 Robberies:http://acm.hdu.edu.cn/showproblem.php?pid=2955
题意: 有n个银行,抢劫第i个银行可以mi的钱,但有pi的风险被抓住, 每个银行只能抢一次,问要在被抓风险不超过P的情况下, 能抢到的最多的钱数是多少。(n <= 100, mi <= 100)
题解:按照裸01背包的想法应该是,dp[i]表示被抓风险不超过i的情况下所能获取最大钱数。但是会发现两个问题:1.对于若干个 银行,求被抓的概率不好算。2.概率是小数,没办法遍历。所以转换思想,对于"被抓风险不超过P", 转换成"不被抓风险 超过1-P",其次,将dp[i]: 设置为获取i金钱,不被抓风险最大为dp[i]. 然后就是熟悉的01背包了。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e4+10;
double dp[maxn];
pair<int, double> a[maxn];
#define fi first
#define se second
int main() {
std::ios::sync_with_stdio(false);
int T;
cin >> T;
while(T--) {
double p; int n;
cin >> p >> n;
p = 1-p;
for(int i = 0; i < maxn; i++) dp[i] = 0;
int sum = 0;
for(int i = 1; i <= n; i++) {
cin >> a[i].fi >> a[i].se;
a[i].se = 1-a[i].se;
sum += a[i].fi;
}
dp[0] = 1;
for(int i = 1; i <= n; i++) {
for(int j = sum; j >= a[i].fi; j--) {
dp[j] = max(dp[j], dp[j-a[i].fi]*a[i].se);
}
}
int res = 0;
for(int i = maxn-1; i >= 0; i--)
if(dp[i] >= p) {
res = i;
break;
}
cout << res << "\n";
}
return 0;
}
3. HDU-2191 http://acm.hdu.edu.cn/showproblem.php?pid=2191
题意: 有资金n元,市场有m中大米,大米只能按袋卖,给出每种大米的 每袋价格p,每袋重量h,袋数c,问最多能买多重的大米。(1 <= n, m <= 100, 1 <= p <= 20, 1 <= h <= 200, 1 <= c <= 20).
题解: 裸的多重背包.
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e2+10;
vector<pair<int, int> > a;
#define fi first
#define se second
int dp[maxn];
void solve(int p, int h, int c) {
int now = 1;
while(c) {
if(c < now) now = c;
a.push_back(make_pair(now*p, now*h));
c -= now;
now <<= 1;
}
}
int main() {
std::ios::sync_with_stdio(false);
int T;
cin >> T;
while(T--) {
a.clear();
int n, m;
cin >> n >> m;
for(int i = 0; i < m; i++) {
int p, h, c;
cin >> p >> h >> c;
solve(p, h, c);
}
memset(dp, 0, sizeof dp);
for(int i = 0; i < a.size(); i++) {
for(int j = n; j >= a[i].fi; j--) {
dp[j] = max(dp[j], dp[j-a[i].fi]+a[i].se);
}
}
int res = 0;
for(int i = 0; i <= n; i++) res = max(res, dp[i]);
cout << res << "\n";
}
return 0;
}
4.HDU-1171 http://acm.hdu.edu.cn/showproblem.php?pid=1171
题意:有n中机器,每种机器有自己的价值V以及数量M,让你将这些机器分成A, B两部分,使得两部分的价值和的差值尽可能小,且A的价值和 >= B的价值和。输出两部分的价值和.
题解: 由于A >= B所以,B <= sum/2, 将问题转换成 用sum/2的资源去换取商品,求能获得商品价值的最大是多少. 转换后就是 裸的多重背包。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 2e5;
int dp[maxn], a[maxn], cnt, sum;
void solve(int val, int num) {
int now = 1;
while(num) {
if(num < now) now = num;
a[cnt++] = val*now;
num -= now; now <<= 1;
}
}
int main() {
int n;
while(scanf("%d", &n) != EOF) {
if(n < 0) break;
cnt = 0, sum = 0;
for(int i = 1; i <= n; i++) {
int val, num;
scanf("%d%d", &val, &num);
solve(val, num);
sum += val*num;
}
int st = sum/2, ans = 0;
memset(dp, 0, sizeof dp);
for(int i = 0; i < cnt; i++) {
for(int j = st; j >= a[i]; j--) {
dp[j] = max(dp[j], dp[j-a[i]]+a[i]);
}
}
ans = dp[st];
printf("%d %d\n", sum-ans, ans);
}
return 0;
}
5.HDU-2639 http://acm.hdu.edu.cn/showproblem.php?pid=2639
题意: 有一个空间为V的背包,有N种骨头,每种骨头有自己的价值与体积,给定一个K求所有装取骨头的方案中,价值第K大的是多少。两种方案的价值和一样则认为是一种方案。 k <= 30, n <= 100, v <= 1000
题解: 和01背包没太大区别,在01背包基础上增加一维dp[i][j]表示: 到目前为止用 i 空间,所能获取价值第j大是多少. 每次枚举i时,将dp[i][1]----dp[i][k] k种方案分别尝试增加当前这个骨头以及不增加,然后去重就好了.
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e3+10;
int dp[maxn][31], val[maxn], w[maxn];
int main() {
std::ios::sync_with_stdio(false);
int T;
cin >> T;
while(T--) {
int n, m, k;
cin >> n >> m >> k;
for(int i = 1; i <= n; i++) cin >> val[i];
for(int i = 1; i <= n; i++) cin >> w[i];
memset(dp, 0, sizeof dp);
vector<int> tmp;
for(int i = 1; i <= n; i++) {
for(int j = m; j >= w[i]; j--) {
tmp.clear();
for(int t = 1; t <= k; t++) {
tmp.push_back(dp[j][t]);
tmp.push_back(dp[j-w[i]][t]+val[i]);
}
sort(tmp.begin(), tmp.end());
int t = tmp.size()-1, now = 1;
while(t >= 0 && now <= k) {
dp[j][now] = tmp[t];
if(now == 1 || dp[j][now] != dp[j][now-1]) now++;
t--;
}
}
}
/* for(int i = 1; i <= k; i++)
cout << dp[m][i] << endl;*/
cout << dp[m][k] << "\n";
}
}
6. codeforces 544 http://codeforces.com/contest/544/problem/C
题意: n个人,有m行代码要写,第i个人 写一行会导致ai个bug,现在要把这m行代码分给n个人,每个人分到的可以为0, 要求总 bug小于等于b.方案数取模. (1 <= n, m <= 500, 0 <= b <= 500)
题解: dp[i][j][k]表示前i个人 bug数为j,写了k行的方案数. 对于当前第i个人考虑 要么不分,要么至少一行,则dp[i][j][k] = dp[i-1][j][k]+dp[i][j-a[i]][k-1]; 然后可以把第一维滚动掉.
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e2+10;
int dp[2][maxn][maxn], a[maxn];
int main() {
std::ios::sync_with_stdio(false);
int n, m, b, mod;
cin >> n >> m >> b >> mod;
for(int i = 1; i <= n; i++)
cin >> a[i];
int last = 1, now = 0;
dp[now][0][0] = 1;
for(int i = 1; i <= n; i++) {
swap(last, now);
dp[now][0][0] = 1;
for(int j = 1; j <= m; j++) {
for(int k = 0; k <= b; k++) {
if(k < a[i]) dp[now][j][k] = dp[last][j][k];
else dp[now][j][k] = (dp[last][j][k]+dp[now][j-1][k-a[i]])%mod;
}
}
}
int res = 0;
for(int i = 0; i <= b; i++)
res = (res+dp[now][m][i])%mod;
cout << res << "\n";
return 0;
}
7.codeforces 864E http://codeforces.com/contest/864/problem/E
题意:有n个文件即将被火烧,每个文件有三个属性: 拯救该文件所需时间ti 被火烧掉的时间点di 价值pi,问最多能拯救多少价值和的文件,并输出价值和 文件个数 拯救顺序。
题解:按照di排序 01背包 更新时记录一下当前文件id以及上一个文件id
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e3+10;
const int maxx = 1e2+10;
struct Node {
int x, y, val, id;
bool operator <(const Node &a) const {
return val < a.val;
}
};
Node dp[maxx][maxn];
struct node {
int t, d, p, id; // cost deadline value id
bool operator <(const node &a) const {
return d < a.d;
}
}a[maxx];
int main() {
std::ios::sync_with_stdio(false);
int n;
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> a[i].t >> a[i].d >> a[i].p;
a[i].id = i;
}
sort(a+1, a+1+n);
for(int i = 1; i <= n; i++) {
for(int j = 0; j < maxn; j++) {
if(j >= a[i].t && j < a[i].d && dp[i-1][j].val < (dp[i-1][j-a[i].t].val+a[i].p)) {
dp[i][j] = (Node){i-1, j-a[i].t, dp[i-1][j-a[i].t].val+a[i].p, a[i].id};
}
else dp[i][j] = dp[i-1][j];
}
}
Node res = dp[n][0];
for(int i = 1; i < maxn; i++)
if(res < dp[n][i]) res = dp[n][i];
vector<int> ans;
cout << res.val << "\n";
while(res.id != 0) {
ans.push_back(res.id);
res = dp[res.x][res.y];
}
cout << ans.size() << "\n";
reverse(ans.begin(), ans.end());
for(int i : ans)
cout << i << " ";
return 0;
}
8. codeforces 730J http://codeforces.com/contest/730/problem/J
题意:有n个杯子,每个杯子体积为ai,当前杯子里有bi的水,现要求把所有的水倒入尽可能少的杯子里,移动一体积的水需要一分钟,求最少的杯子数量,以及在该数量下的最少时间。(1<=n<=100, 1<=bi <= ai <= 100)
题解:首先最少的杯子数量好求,贪心拿体积最大的杯子就行, 那我们把杯子体积看作价值,已有的水,和杯子的数量看作花费
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e4+10;
struct node {
int a, b;
}p[105];
bool cmp_b(node x, node y) {
return x.b > y.b;
}
int dp[105][maxn], sum_b[maxn], sum_a[maxn];
int f(int len, int sum) {
int l = 1, r = len, ans = 0;
while(l <= r) {
int mid = (l+r)>>1;
if(sum_b[mid] >= sum) ans = mid, r = mid-1;
else l = mid+1;
}
return ans;
}
int main() {
std::ios::sync_with_stdio(false);
int n;
cin >> n;
for(int i = 1; i <= n; i++) cin >> p[i].a;
for(int i = 1; i <= n; i++) cin >> p[i].b;
sort(p+1, p+1+n, cmp_b);
sum_b[0] = 0, sum_a[0] = 0;
for(int i = 1; i <= n; i++) {
sum_b[i] = sum_b[i-1]+p[i].b;
sum_a[i] = sum_a[i-1]+p[i].a;
}
int kk = f(n, sum_a[n]);
memset(dp, -1, sizeof dp);
dp[0][0] = 0;
for(int i = 1; i <= n; i++) {
for(int j = sum_a[i]; j >= p[i].a; j--) {
for(int k = kk; k >= 1; k--)
if(dp[k-1][j-p[i].a] != -1) dp[k][j] = max(dp[k][j], dp[k-1][j-p[i].a]+p[i].b);
}
}
int ans;
for(int i = sum_a[n]; i >= 0; i--)
if(dp[kk][i] >= sum_a[n]) {
ans = sum_a[n]-i;
break;
}
cout << kk << " " << ans << "\n";
return 0;
}