zoukankan      html  css  js  c++  java
  • 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,使用二进制分解将「 值域极大,但是对象种类很少」的问题化大为小。

  • 相关阅读:
    User Get 'Access Denied' with Excel Service WebPart
    How To Search and Restore files from Site Collection Recycle Bin
    How To Collect ULS Log from SharePoint Farm
    How To Restart timer service on all servers in farm
    How to Operate SharePoint User Alerts with PowerShell
    How to get Timer Job History
    Synchronization Service Manager
    SharePoint 2007 Full Text Searching PowerShell and CS file content with SharePoint Search
    0x80040E14 Caused by Max Url Length bug
    SharePoint 2007 User Re-created in AD with new SID issue on MySite
  • 原文地址:https://www.cnblogs.com/Neal-lee/p/15428029.html
Copyright © 2011-2022 走看看