「动态规划」欠债还钱

本题是一道背包上限可以变化的多重部分和问题,此处给出了粗略题解

题目来源:(未知)

题面

题目描述

llk经常和wy一起去yh小饭馆吃盖浇饭,一天他们吃完后llk把两个人的钱一起付了,但是wy不想欠llk的钱。

现在wy手中有一些散钱,llk手中也有一些散钱,wy想知道能不能刚好使得两不相欠,但是wy很笨,你能帮助wy吗?

输入

多组测试数据,每组第一行输入3个非负整数,C,n,m。C代表wy欠llk的钱,n代表wy手中钱面值的种类,m代表llk手中钱面值的种类。

接下来的n行,每行两个数v, c,分别代表wy手中面值为v的钱币有c个。再接下来的m行,每行两个数v,c,分别代表llk手中面值为v的钱币有c个。

(C <= 10000; 1<=n, m<50; 0<=v < =100; 0<=c<=10 )

输出

每组数据输出一行,如果存在一种方案使得wy和llk两不相欠,输出YES,否则输出NO。

样例输入

7 1 1
10 1
1 10

样例输出

YES

提示

wy给了llk一张10元的,llk又给了wy三个1元的


思路分析

显然,这题是背包问题中比较经典的多重部分和问题,应使用动态规划算法解决.

对于普通的多重部分和问题,通常只需要借助一个布尔类型的数组来进行状态转移即可,当然也可以通过把状态定义为当前放满背包后还剩多少个物品,通过记录更多的状态信息来优化时间(此处采用的此写法).

不过特殊的地方在于此处llk会进行找零.找零可以看成是在扩大背包容量的上限,对此我们只需要假设llk的所有钱都用于找零,将背包容量扩到最大,然后对这个最大的背包先进行一次正常的多重部分和问题的状态转移,随后再依据llk的找零会扩大背包容量这一点,进行一次逆向的状态转移,从扩大后的容量推出当前扩大后是否可以放满即可.

参考代码

#include <iostream>
#include <cstring>
#include <vector>

using namespace std;

int main()
{
    ios::sync_with_stdio(false);

    int c, n, m;
    while (cin >> c >> n >> m)
    {
        pair<int, int> *money = new pair<int, int>[n + m]; // 存放输入的钱和数量
        int limit = 0;                                     // 背包容量可扩上限(因为llk会改变背包容量)

        // 输入钱的面值和数量
        for (int i = 0; i < n + m; i++)
        {
            cin >> money[i].first >> money[i].second;
            if (i >= n)
            {
                limit += money[i].first * money[i].second; // 调整背包容量上限
            }
        }

        // 初始状态
        vector<int> dp(c + limit + 1, -1);
        dp[0] = 0; // new一个数组是会re,所以此处用vector

        // 状态转移wy,状态定义为当前钱放满之后剩多少个,<0为放不了
        for (int i = 0; i < n; i++) // 第i种钱
        {
            for (int j = 0; j <= c + limit; j++) // 背包上限为j
            {
                if (dp[j] >= 0) // 已经满了
                {
                    dp[j] = money[i].second; // 当前全部剩下
                }
                else if (j < money[i].first || dp[j - money[i].first] <= 0) // 放不满
                {
                    dp[j] = -1;
                }
                else // 前面多出来的一个使之放满,所以剩下的少一个
                {
                    dp[j] = dp[j - money[i].first] - 1;
                }
            }
        }

        // 状态转移llk,状态定义为还能用第i种钱减小几次使得背包能放满,<0为减小了也放不了
        for (int i = n; i < m + n; i++) // 第i种钱
        {
            for (int j = c + limit; j >= c; j--) // 用llk的钱来扩大背包总容量,需要当次后面的数据,所以要倒着
            {
                if (dp[j] >= 0) // 已经满了
                {
                    dp[j] = money[i].second; // 当前全部剩下
                }
                else if (j + money[i].first > c + limit || dp[j + money[i].first] <= 0) // 扩大不了
                {
                    dp[j] = -1;
                }
                else // 用前面多出来的一个扩大背包总容量,所以剩下的少一个
                {
                    dp[j] = dp[j + money[i].first] - 1;
                }
            }
        }

        cout << (dp[c] >= 0 ? "YES" : "NO") << "\n"; // 检查下dp[c]的状态对不对即可
        delete money;
    }
    return 0;
}

"正是我们每天反复做的事情,最终造就了我们,优秀不是一种行为,而是一种习惯" ---亚里士多德

posted @ 2022-11-19 16:56  星双子  阅读(57)  评论(0编辑  收藏  举报