• 硬币思考多重背包⭐


    题面

    给定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;
    }
    
  • 相关阅读:
    Socket原理与编程基础
    Hello cnblogs
    c# List 分页问题
    chrome下载Word失败问题
    前端时间Date显示问题踩坑
    Vue跳转同一界面多次,使用不同数据进行渲染
    Hadoop在Linux环境下的配置
    RabbitMQ下载安装
    Codeforces 527 C. Glass Carving
    python压缩、解压文件
  • 原文地址:https://www.cnblogs.com/2aptx4869/p/12847008.html
Copyright © 2020-2023  润新知