zoukankan      html  css  js  c++  java
  • 硬币思考多重背包⭐

    题面

    给定N种硬币,其中第 i 种硬币的面值为Ai,共有Ci个。

    从中选出若干个硬币,把面值相加,若结果为S,则称“面值S能被拼成”。

    求1~M之间能被拼成的面值有多少个。

    输入格式

    输入包含多组测试用例。

    每组测试用例第一行包含两个整数N和M。

    第二行包含2N个整数,分别表示A1,A2,…,AN和C1,C2,…,CN。

    当输入用例N=0,M=0时,表示输入终止,且该用例无需处理。

    输出格式

    每组用例输出一个结果,每个结果占一行。

    数据范围

    1≤N≤100,
    1≤M≤105,
    1≤Ai≤105,
    1≤Ci≤1000
    

    输入用例:

    3 10
    1 2 4 2 1 1
    2 5
    1 4 2 1
    0 0
    

    输出用例:

    8
    4
    

    思考

    先不看题

    01背包

    对于多重背包, 可以拆成01背包来做

    unsigned int f[maxn];
    
    memset(f, 0, sizeof f);
    f[0] = 0;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= c[i]; ++j)
            for (int k = m; k >= v[i]; --k)
                f[k] = max(f[k], f[k - v[i]] + w[i]);
        
    int ans = 0;
    for (int i = 0; i <= m; ++i)
        ans = max(maxn, f[i]);
    

    二进制拆法

    (C_i)拆成p + 2个物品

    (2^0) * (V_i), (2^1) * (V_i), ... , (2^p) * (V_i), (R_i) * (V_i)

    虽然没队列优化快,但是好写

    unsigned int f[maxn];
    
    int nw[maxn * log2(maxn)], nv[maxn * log2(maxn)];
    int cnt = 0;
    for (int i = 1; i <= n; ++i)
    {
        for (int j = 1; j <= c[i]; j <<= 1)
        {
            nw[++cnt] = j * w[i];
            nv[cnt] = j * v[i];
            c[i] -= j;
        }
        if (s[i]) nw[++cnt] = s[i] * w[i], nv[cnt] = s[i] * v[i];
    }
    
    memset(f, 0, sizeof f);
    for (int i = 1; i <= cnt; ++i)
        for (int j = m; j >= nv[i]; --j)
            f[j] = max(f[j], f[j - nv[i]] + nw[i]); 
    

    单调队列

    #include <bits/stdc++.h>
    using namespace std;
    
    const int maxn = 2005;
    
    int n, v[maxn], w[maxn], c[maxn], q[maxn], m, f[maxn];
    
    int calc(int u, int i, int k)
    {
        return f[u + k * v[i]] - k * w[i];
    }
    
    int main()
    {
        scanf("%d%d", &n, &m);
        memset(f, 0xcf, sizeof f);
        f[0] = 0;
        for (int i = 1; i <= n; ++ i)
        {
            scanf("%d%d%d", v + i, w + i, c + i);
            for (int u = 0; u < v[i]; ++u)
            {
                int l = 1, r = 0;
                int maxp = (m - u) / v[i];
                for (int k = maxp - 1; k >= max(maxp - c[i], 0); -- k)
                {
                    while (l <= r && calc(u, i, q[r]) <= calc(u, i, k)) --r;
                    q[++r] = k;
                }
                for (int p = maxp; p; --p)
                {
                    while (l <= r && q[l] > p - 1) ++ l;
                    if (l <= r) f[u + p * v[i]] = max(f[u + p * v[i]], calc(u, i, q[l]) + p * w[i]);
                    if (p - c[i] - 1 >= 0)
                    {
                        while (l <= r && calc(u, i, q[r]) <= calc(u, i, p - c[i] - 1)) --r;
                        q[++r] = p - c[i] - 1;
                    }
                }
            }
        }
        int ans = 0;
        for (int i = 1; i <= m; ++ i) ans = max(ans, f[i]);
        printf("%d", ans);
        return 0;
    }
    

    题解

    好了来看题, 一定要注意到我们只关注可行性,而不关注最优解

    暴力01背包

    bool f[maxn];
    memset(f, 0, sizeof);
    f[0] = 1;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= c[i]; ++j)
            for (int k = m; k >= a[i]; --k)
                f[k] |= f[k - a[i]];
    

    必定超时, 但是这道题是可行性为题, 不需要最优解

    我们发现,若前i种硬币能凑出 sum/2, 只有两种情况:

    1.在i阶段之前, 就已经f[j] = true

    2.在i阶段之前, 就已经f[j - i] = true

    于是,可以贪心, 设used[j] 表示f[j]在阶段i是为true的情况下至少需要多少块i种硬币

    这样上面的代码 for(j) for(k) 循环可以优化为1维,直接正序扫面

    当(!f[j] && f[j - i] && used[j - i] < a[i])才可以转移

    巧妙利用其只求可行性,既不二分也不单调队列

    #include <bits/stdc++.h>
    using namespace std;
    
    const int maxn = 1e5 + 5;
    
    int a[101], c[101], n, m;
    int f[maxn], cnt, used[maxn];
    
    int main()
    {
    	while (cin >> n >> m, n && m)
    	{
    		for (int i = 1; i <= n; ++i) cin >> a[i];
    		
    		memset(f, 0, sizeof f); 
    		f[0] = 1; cnt = 0;
    		for (int i = 1; i <= n; ++i)
    		{
    			cin >> c[i];
    			
    			for (int j = 0; j <= m; ++j) used[j] = 0;
    			
    			for (int j = a[i]; j <=m ; ++ j) 
    			    if (!f[j] && f[j - a[i]] && used[j - a[i]] < c[i]) 
    			        f[j] = 1, used[j] = used[j - a[i]] + 1, ++cnt;
    		}
    		cout << cnt << '
    ';
    	}
    	return 0;
    }
    
  • 相关阅读:
    数据类型装换
    变量及数据类型
    27 网络通信协议 udp tcp
    26 socket简单操作
    26 socket简单操作
    14 内置函数 递归 二分法查找
    15 装饰器 开闭原则 代参装饰器 多个装饰器同一函数应用
    12 生成器和生成器函数以及各种推导式
    13 内置函数 匿名函数 eval,exec,compile
    10 函数进阶 动态传参 作用域和名称空间 函数的嵌套 全局变量
  • 原文地址:https://www.cnblogs.com/2aptx4869/p/12847008.html
Copyright © 2011-2022 走看看