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

题目

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

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了,然后发现题目并没有对某一物品的使用次数进行限制。所以这里要注意,背包问题读题时的一个问题,就是应该抠字眼扣清楚每个物品使用次数,来区分各种背包模型。

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 
#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;
}

你可能感兴趣的:(其它,7.DP专项练习)