以背包为标签,搜出了这么多题,按难度排序,一道一道做:
(*):下面有提到
TODO | 题目 | 难度 | 备忘录 |
---|---|---|---|
AC | 采药 | 普及- | 01背包模板 |
AC | 开心的金明 | 普及- | 01背包模板 |
AC | 小A点菜 | 普及- | 背包方案数问题 |
AC | NASA的食物计划 | 普及- | 简单的二维费用背包 |
AC | 疯狂的采药 | 普及- | 完全背包问题 |
AC | 通天之分组背包 | 普及- | 分组背包模板 |
AC | 神奇的四次方数 | 普及- | 简单的判断型完全背包(*) |
AC | 最大约数和 | 普及- | 简单的01背包+预处理 |
AC | A+B Problem(再升级) | 普及- | 完全背包方法数(*) |
AC | [HAOI2012]音量调节 | 普及- | 刷表法的多阶段决策问题 |
AC | 小书童——刷题大军 | 普及- | 两个01背包 |
AC | 三角形牧场 | 提高- | 01背包,已知推未知的状压DP |
AC | 搭配购买 | 提高- | 连通块的01背包(*) |
AC | 集合 Subset Sums | 提高- | 二维费用方法数(*) |
AC | 积木城堡 | 提高- | 倒的01背包:背包刷表法 |
AC | 投资的最大效益 | 提高- | 背包的实际应用:多阶段的多个多重背包(*) |
AC | 魔术棋子 | 提高- | 多阶段决策,过程取模 |
AC | 机器分配 | 提高- | 多阶段决策的最小字典序最优方案 |
yyy2015c01的U盘 | 提高- | ||
邮票 Stamps | 提高- | ||
奶酪塔 | 提高- | ||
AC | 金明的预算方案 | 提高 | 简单的依赖背包,靠枚举 |
AC | 烹调方案 | 提高 | 价值变换的背包 |
选学霸 | 提高 | ||
哞哞哞Mooo Moo | 提高 | ||
虫洞Wormholes | 提高 | ||
牛的过山车 | 提高 | ||
股票市场 | 提高 | ||
拖拉机Tractor | 提高 | ||
赛斯石(赛后强化版) | 提高 | ||
不开心的金明 | 提高 | ||
AC | 垃圾陷阱 | 省选- | 复杂二维费用,状态定义与转移问题 |
AC | 多米诺骨牌 | 省选- | 抽象出来凑差值的01背包 |
宝物筛选 | 省选- | ||
飞扬的小鸟 | 省选- | ||
产品加工 | 省选- | ||
计算器写作文 | 省选- | ||
豪华游轮 | 省选- | ||
商店购物 | 省选- | ||
最少的硬币 | 省选- | ||
挤奶的时间 | 省选- | ||
小Q的棋盘 | 省选- | ||
Eden 的新背包问题 | 省选- | ||
挂饰 | 省选- | ||
消失之物 | 省选- | ||
采摘毒瘤 | 省选- | ||
小A的时钟 | 省选 | ||
语文-理理思维 | 省选 | ||
梦幻岛宝珠 | 省选 | ||
星空 | 省选 | ||
魔兽地图 | 省选 | ||
粉刷匠 | 省选 | ||
基因匹配 | 省选 | ||
苹果树 | NOI+ | ||
付公主的背包 | NOI+ | ||
秘密袭击coat | NOI+ |
PASS掉的其它的题:
(大概就是很简单或者重复的题目类型,有限的人生就不浪费在这些水题上了)
TODO | 题目 | 难度 | 备忘录 |
---|---|---|---|
PASS | 混合牛奶 | 普及- | 01背包模板 |
PASS | 扑克牌 | 普及- | 数学题 |
PASS | 总分 Score Inflation | 普及- | USACO的01背包模板 |
AC | L国的战斗之间谍 | 普及- | 简单的二维费用 |
PASS | kkksc03考前临时抱佛脚 | 普及- | 不像是背包 |
PASS | Bessie的体重问题 | 普及- | 01背包模板 |
PASS | 手链 | 普及- | 01背包模板 |
PASS | 干草出售 | 普及- | 01背包模板 |
AC | 榨取kkksc03 | 提高- | 简单的二维费用 |
PASS | 精卫填海 | 提高- | 简单的01背包 |
PASS | 樱花 | 提高- | 混合背包 |
AC | [AHOI2001]质数和分解 | 提高- | 重复,同A+B升级版 |
PASS | 珍珠配对 | 提高- | 没有spj |
PASS | 跨河 | 提高- | 不适合用背包做 |
PASS | 买干草 | 提高- | 裸完全背包 |
PASS | 牛飞盘队 | 提高- | 01背包方法数裸题 |
PASS | 购买巧克力 | 提高- | 贪心题 |
LP1679
刚开始以为是01背包,然后就WA了,然后发现题目并没有对某一物品的使用次数进行限制。所以这里要注意,背包问题读题时的一个问题,就是应该抠字眼扣清楚每个物品使用次数,来区分各种背包模型。
1.给定一个正整数n,求将其分解成若干个素数之和的方案总数。
这题都能爆int你敢信?。。
当n=1000时,方案数为48278613741845757。
所以长点心。。这种很迷的数学题要经常记得用longlong。
2.简单的验证素数方法:
bool flag = true;
for (int i = 2; i <= trunc(sqrt(num)); i++)
if (num % i == 0) flag = false;
// trunc:小数去尾并变成int
LP1877
本题用的是刷表法,用d[i][j]来更新d[i+1][j+v]和d[i+1][j-v]。由于决策往前往后的都用,不能确定滚动数组的枚举顺序。硬要用滚动数组的话,可以用一个二维的滚动数组。
P1455,连通块01背包,代码放这以供参考。
#include
#include
#include
#include
#include
#include
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;
const int maxn = 10000 + 10;
vector<int> edge[maxn];
int n, m, c, v[maxn], w[maxn], cc, nv[maxn], nw[maxn], vis[maxn], d[maxn];
void dfs(int u, int cnt) {
nv[cnt] += v[u];
nw[cnt] += w[u];
vis[u] = 1;
for(vector<int>::iterator iter = edge[u].begin(); iter!=edge[u].end(); ++iter)
if (!vis[*iter]) {
dfs(*iter, cnt);
}
}
int main() {
scanf("%d%d%d", &n, &m, &c);
_rep(i, 1, n) scanf("%d%d", &v[i], &w[i]);
int u, v;
_for(i, 0, m) {
scanf("%d%d", &u, &v);
edge[u].push_back(v);
edge[v].push_back(u);
}
cc = 0;
_rep(i, 1, n)
if (!vis[i])
dfs(i, cc++);
_for(i, 0, cc)
for (int j = c; j >= nv[i]; j--)
d[j] = max(d[j], d[j - nv[i]] + nw[i]);
printf("%d\n", d[c]);
return 0;
}
LP1466
小题的细节问题:即便是这样简单题,也应该写对拍。
错误代码:(n=5时,返回1)
int main() {
scanf("%d", &n);
d[0][0] = 1;
int s = n * (n + 1) / 4;
_rep(i, 1, n)
for (int j = s; j >= 0; j--)
for (int k = s; k >= 0; k--) {
if (j >= i)
d[j][k] += d[j - i][k];
if (k >= i)
d[j][k] += d[j][k - i];
}
printf("%lld\n", d[s][s] / 2);
return 0;
}
正确代码:
#include
#include
#include
#include
#include
#define ll long long
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;
const int maxn = 1000 + 10;
int n;
ll d[maxn][maxn];
int main() {
scanf("%d", &n);
d[0][0] = 1;
int s = n * (n + 1) / 4;
if (n*(n + 1) == s * 4) {
_rep(i, 1, n)
for (int j = s; j >= 0; j--)
for (int k = s; k >= 0; k--) {
if (j >= i)
d[j][k] += d[j - i][k];
if (k >= i)
d[j][k] += d[j][k - i];
}
printf("%lld\n", d[s][s] / 2);
}
else {
printf("0\n"); // 无法成功二分,自然方案数为0
}
return 0;
}
LP1853
本题可以说是背包的实际应用,多阶段决策问题,而每个阶段的决策要选最佳,选最佳就需要一个多重背包。
本题需要注意的是预估数组范围声明。可以看到本题需要声明个d[maxn],但是maxn不好确定,题目只给出初始资金的范围,那么需要仔细的算一下最后资金的范围:注意此处为了防止卡数据,一定在内存富裕的情况下往大里声明。
举个例子,本题我估算了以下maxn最大是1000(当然是错的),于是我就声明了个1000,一半的点RE了。。。只能说,这种题,1000占太少内存了,直接声明到100000来保险。
本题作为背包的实际应用场景,代码可以参考
#include
#include
#include
#include
#include
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;
const int maxw = 10000;
const int maxd = 10 + 3;
int c, t, n, v[maxd], w[maxd], cnt[maxd], d[maxw];
int main() {
scanf("%d%d%d", &c, &t, &n);
int a, b;
_for(i, 0, n) {
scanf("%d%d", &a, &b);
v[i] = a / 1000, w[i] = b;
}
int cc = c / 1000, rcc = c;
_for(tt, 0, t) {
memset(d, 0, sizeof(d));
_for(i, 0, n) {
cnt[i] = cc / v[i];
int k = 1, amount = cnt[i];
while (k < amount) {
for (int j = cc; j >= v[i] * k; j--)
d[j] = max(d[j], d[j - v[i] * k] + w[i] * k);
amount -= k;
k *= 2;
}
for (int j = cc; j >= v[i] * amount; j--)
d[j] = max(d[j], d[j - v[i] * amount] + w[i] * amount);
}
int ans = 0;
_rep(i, 0, cc) ans = max(ans, d[i]);
rcc += ans;
cc = rcc / 1000;
}
printf("%d\n", rcc);
return 0;
}
LP1504
本题的实际应用情况就不说了,有一个点。
本题是倒着的01背包,也就是从满的背包种往出拿东西,并且是判断性的DP。这里考虑的是刷表法,如果d[j]存在,那么d[j-v]也存在。
这里注意的是,由于是这样的逆的刷表法,所以j的枚举顺序都相反了,所以原本01背包的j:c->v,应该改成j:v->c。类似,其它种类的背包,也都需要颠倒枚举顺序。
#include
#include
#include
#include
#include
#include
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;
const int maxn = 100 + 10;
const int maxm = 10000 + 10;
vector<int> item[maxn];
int n, sum[maxn], d[maxn][maxm];
int main() {
scanf("%d", &n);
_for(i, 0, n) {
int v;
while (1) {
scanf("%d", &v);
if (v == -1) break;
item[i].push_back(v);
sum[i] += v;
}
}
_for(i, 0, n) {
d[i][sum[i]] = 1;
for (vector<int>::iterator iter = item[i].begin(); iter != item[i].end(); ++iter) {
for (int j = *iter; j <= sum[i]; j++)
if (d[i][j])
d[i][j - *iter] = 1;
}
}
int ans = 0;
_for(i, 0, n) ans = max(ans, sum[i]);
for (; ans >= 0; ans--) {
bool b = true;
_for(i, 0, n) if (!d[i][ans]) b = false;
if (b) break;
}
printf("%d\n", ans);
return 0;
}
这里有一个常数优化,考虑数组cnt[i]表示高度为i的城堡的个数。当i==n时,i是一个合法的解。
#include
#include
#include
#include
#include
#include
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;
const int maxn = 100 + 10;
const int maxm = 10000 + 10;
vector<int> item[maxn];
int n, sum[maxn], d[maxm], cnt[maxm];
int main() {
scanf("%d", &n);
_for(i, 0, n) {
int v;
while (1) {
scanf("%d", &v);
if (v == -1) break;
item[i].push_back(v);
sum[i] += v;
}
}
_for(i, 0, n) {
memset(d, 0, sizeof(d));
d[sum[i]] = 1;
cnt[sum[i]]++;
for (vector<int>::iterator iter = item[i].begin(); iter != item[i].end(); ++iter) {
for (int j = *iter; j <= sum[i]; j++)
if (d[j]) {
if (!d[j - *iter]) cnt[j - *iter]++;
d[j - *iter] = 1;
}
}
}
int ans = 0;
_for(i, 0, n) ans = max(ans, sum[i]);
for (; ans >= 0; ans--) {
if (cnt[ans] == n) break;
}
printf("%d\n", ans);
return 0;
}
LP2049
首先本题有个过程取模。可以得出的结论是,乘法不会改变模。
另外是注意对数据的输入时预处理,要先把每个整数取了mod再输入。
#include
#include
#include
#include
#include
#include
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;
const int maxn = 100 + 10;
int n, m, k, G[maxn][maxn];
vector<int> d[maxn][maxn]; // 本题数据很小,放心用vector
int main() {
scanf("%d%d%d", &n, &m, &k);
int a;
_rep(i, 1, n)
_rep(j, 1, m) {
scanf("%d", &a);
a %= k;
G[i][j] = a;
d[i][j].assign(k, 0);
}
d[1][1][G[1][1]] = 1;
_rep(i,1,n)
_rep(j,1,m)
_for(num,0,k)
if (d[i][j][num]) {
if (i + 1 <= n) d[i + 1][j][num * G[i + 1][j] % k] = 1;
if (j + 1 <= m) d[i][j + 1][num * G[i][j + 1] % k] = 1;
}
int ans = 0;
_for(i, 0, k)
if (d[n][m][i])
ans++;
printf("%d\n", ans);
_for(i, 0, k)
if (d[n][m][i])
printf("%d ", i);
return 0;
}