[背包DP] 洛谷相关题目整理与练习(74题-)

题目

以背包为标签,搜出了这么多题,按难度排序,一道一道做:
(*):下面有提到

TODO题目难度备忘录
AC采药普及-01背包模板
AC开心的金明普及-01背包模板
AC小A点菜普及-背包方案数问题
ACNASA的食物计划普及-简单的二维费用背包
AC疯狂的采药普及-完全背包问题
AC通天之分组背包普及-分组背包模板
AC神奇的四次方数普及-简单的判断型完全背包(*)
AC最大约数和普及-简单的01背包+预处理
ACA+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+
秘密袭击coatNOI+

PASS掉的其它的题:
(大概就是很简单或者重复的题目类型,有限的人生就不浪费在这些水题上了)

TODO题目难度备忘录
PASS混合牛奶普及-01背包模板
PASS扑克牌普及-数学题
PASS总分 Score Inflation普及-USACO的01背包模板
ACL国的战斗之间谍普及-简单的二维费用
PASSkkksc03考前临时抱佛脚普及-不像是背包
PASSBessie的体重问题普及-01背包模板
PASS手链普及-01背包模板
PASS干草出售普及-01背包模板
AC榨取kkksc03提高-简单的二维费用
PASS精卫填海提高-简单的01背包
PASS樱花提高-混合背包
AC[AHOI2001]质数和分解提高-重复,同A+B升级版
PASS珍珠配对提高-没有spj
PASS跨河提高-不适合用背包做
PASS买干草提高-裸完全背包
PASS牛飞盘队提高-01背包方法数裸题
PASS购买巧克力提高-贪心题

理解

神奇的四次方数

LP1679
刚开始以为是01背包,然后就WA了,然后发现题目并没有对某一物品的使用次数进行限制。所以这里要注意,背包问题读题时的一个问题,就是应该抠字眼扣清楚每个物品使用次数,来区分各种背包模型。

A+B Problem(再升级)

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
[HAOI2012]音量调节

LP1877
本题用的是刷表法,用d[i][j]来更新d[i+1][j+v]和d[i+1][j-v]。由于决策往前往后的都用,不能确定滚动数组的枚举顺序。硬要用滚动数组的话,可以用一个二维的滚动数组

搭配购买

P1455,连通块01背包,代码放这以供参考。

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#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 <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#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 <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#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 <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#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 <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#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 <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#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;
}
  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值