• Topcoder 13859 ClassicProblem


    题目链接

    Topcoder 13859 ClassicProblem

    题目大意

    背包问题,有 (n) 种物品,第 (i) 种物品有 (cnt_i) 个,重量 (w_i),价值 (v_i),背包容量 (limit),求能放下的物品最大价值总和。

    (1leq nleq 80)(1leq cnt_ileq 10^9)(1leq w_ileq 80)(1leq v_ileq 10^9)(1leq limitleq 10^9)

    法一

    物品种类很少,重量范围也很小,想象一下填充的过程,可以发现大部分地方都是可以直接按照性价比((frac{v_i}{w_i}))从大到小放的,只有最后的一点空隙那边,直接放会出现无法充分利用空余空间的情况,需要去做背包 (DP)

    具体来说,若 (frac{v_i}{w_i}<frac{v_j}{w_j}),那么把 (w_j) 个物品 (i) 替换成 (w_i) 个物品 (j) 是更优的,所以在所有物品的数量都 (<max{w_i}) 的时候贪心策略才不适用,这时 (cnt_ileq 80),而 (limitleq sum cnt_iw_ileq 512000),值域较小,可以直接做多重背包 (DP) 了,单调队列或二进制优化都是可以通过时限的,最后枚举 (DP) 部分占了多少空间,剩下部分贪心即可。

    时间复杂度 (O(n^2W^2log W))(O(n^2W^2))

    Code

    // O(n^2*W^2logW)
    #include<iostream>
    #include<vector>
    #include<algorithm>
    #include<cstring>
    #define mem(a,b) memset(a, b, sizeof(a))
    #define rep(i,a,b) for(int i = (a); i <= (b); i++)
    #define per(i,b,a) for(int i = (b); i >= (a); i--)
    #define N 82
    #define W 80
    #define ll long long
    using namespace std;
    
    class ClassicProblem{
        public:
        ll dp[N*W*W];
        struct item{
            ll cnt, w, v;
            bool operator < (const item b) const{ return v*b.w > b.v*w; }
        } a[N];
        vector<item> items;
        ll maximalValue(vector<int> cnt, vector<int> w, vector<int> v, int limit){
            int n = cnt.size();
            rep(i,0,n-1) a[i].cnt = cnt[i], a[i].w = w[i], a[i].v = v[i];
            sort(a, a+n);
            rep(i,0,n-1){
                int x = min(cnt[i], W);
                for(ll p = 1; p <= x; p <<= 1)
                    items.push_back({1, w[i]*p, v[i]*p}), x -= p;
                if(x) items.push_back({1, (ll)w[i]*x, (ll)v[i]*x});
            }
            for(item t : items) per(i,n*W*W,t.w) dp[i] = max(dp[i], dp[i-t.w] + t.v);
            ll ans = 0;
            rep(s,0, min(n*W*W, limit)){
                ll lft = limit-s, tot = 0;
                rep(i,0,n-1){
                    ll num = min(max(0ll, a[i].cnt-W), lft/a[i].w);
                    tot += num*a[i].v, lft -= num*a[i].w;
                }
                ans = max(ans, tot+dp[s]);
            }
            return ans;
        }
    } solve;
    

    法二

    法一是挺好想的做法,然而本人开始实现错了,误以为做法无法保证真确性,于是就有了法二。

    考虑直接对它 (DP),如此大的值域容易想到 (log) 做法,于是考虑二进制分解,这里不仅要对 (cnt) 分解,也要对 (limit) 二进制分解,相当于数位 (DP) 里的一位位计算了。

    (dp_{p,i}) 表示当前考虑到了第 (p) 位,在第 (p) 位往上还剩 (s) 的容量,即实际上是剩 (2^ps) 的容量。

    先对 (cnt_i) 二进制分解,要对应到每个位上,因此分解后不能剩下余项,当前位为 (1) 时拆 (1) 个,当前位为 (0) 时拆 (2) 个。这样 (i) 的上限就应为 (2cdot 2n W),前面的 (2) 是考虑进位的情况。常规地,(DP) 要压着上限做,所以是从高位往低位转移 (dp)

    • (b)(limit) 在第 (p) 位的值, (dp_{p,i}leftarrow dp_{p+1,lfloorfrac{i-1+b}{2} floor})
    • 正常背包: (dp_{p,i}leftarrow dp_{p,i+w_j}+2^pv_j)

    最后 (dp_{0,i}) 中的最大值即为答案。

    时间复杂度 (O(log Vn^2W))

    这个时间复杂度应该是比法一更加优秀的,本人的实现法二比法一快了 (16) 倍多一点。

    // O(logV*n^2*W)
    #include<iostream>
    #include<vector>
    #include<algorithm>
    #include<cstring>
    #define mem(a,b) memset(a, b, sizeof(a))
    #define rep(i,a,b) for(int i = (a); i <= (b); i++)
    #define per(i,b,a) for(int i = (b); i >= (a); i--)
    #define N 82
    #define LOG 30
    #define ll long long
    #define PII pair<int, int>
    #define fr first
    #define sc second
    using namespace std;
    
    class ClassicProblem{
        public:
        ll dp[LOG+5][4*N*N];
        vector<PII> knapsack[LOG+5];
        ll maximalValue(vector<int> cnt, vector<int> w, vector<int> v, int limit){
            int n = cnt.size(), mx = 0;
            rep(i,0,n-1){
                mx = max(mx, w[i]);
                rep(p,0,LOG){
                    if(!cnt[i]) break;
                    rep(_,1,2-cnt[i]%2) knapsack[p].push_back({w[i], v[i]});
                    if(cnt[i]&1) cnt[i] >>= 1;
                    else cnt[i] >>= 1, cnt[i]--;
                }
            }
            mem(dp, 0x80), dp[LOG][0] = 0;
            per(p,LOG,0){
                int b = (limit>>p)&1;
                rep(i,0,4*n*mx) dp[p][i] = max(dp[p][i], dp[p+1][(i-b+1)/2]);
                for(PII a : knapsack[p]) rep(i,0,4*n*mx-a.fr) if(dp[p][i+a.fr] >= 0)
                    dp[p][i] = max(dp[p][i], dp[p][i+a.fr] + (1ll<<p)*a.sc);
            }
            ll ans = 0;
            rep(i,0,4*n*mx) ans = max(ans, dp[0][i]);
            return ans;
        }
    } solve;
    

    和法二思路类似的题目我记得还有 CF1290F Making Shapes,使用二进制分解将「 值域极大,但是对象种类很少」的问题化大为小。

  • 相关阅读:
    孤儿进程与僵尸进程
    python with as的用法
    工作目录与os.getcwd()
    内置模块
    迭代器,生成器
    表达式,语句
    字符流
    字节流
    File
    触发器的操作
  • 原文地址:https://www.cnblogs.com/Neal-lee/p/15428029.html
Copyright © 2020-2023  润新知