• [ACNOI2022]物品


    题目

    题目背景
    当你感到疑惑时,不妨向神明祈祷;神可以回答一切问题。

    O n e I n D a r k \sf OneInDark OneInDark:“卷爷为什么这么卷啊?”
    O U Y E \sf OUYE OUYE:“没有比这更质朴的快乐了。”

    D D G \sf DDG DDG:“天天说别人二次元,有意思没有嘛。”
    O U Y E \sf OUYE OUYE:“这难道不令你感到舒适吗?”

    最后是『神明三连击』:
    c r a s h e d \sf crashed crashed:“哦,忘记给你说了……”
    Tiw-Air-OAO \textsf{Tiw-Air-OAO} Tiw-Air-OAO:“你看到的只是表象……”
    O U Y E \sf OUYE OUYE:“本质是一样的!”

    题目描述
    重量为 k    ( − m ⩽ k ⩽ m ) k\;(-m\leqslant k\leqslant m) k(mkm) 的物品有 a k a_k ak 个,求重量和恰为 L L L 的最多物品数,或报告无解。

    数据范围与提示
    m ⩽ 200 m\leqslant 200 m200 ∣ L ∣ ⩽ 1 0 18 |L|\leqslant 10^{18} L1018 以及 a k ⩽ 1 0 12 a_k\leqslant 10^{12} ak1012

    思路

    背包问题的最好优化方式是 balance \textit{balance} balance 。这个在子集和问题的论文中有说到。

    简单来说,可以让背包重量始终在 L L L 附近徘徊。因此我们可以略去很多无用信息。

    这道题也比较像,考虑我们需要怎样的调整。但套用那样的方法,得不到好的结果,因为权值与重量并不挂钩。

    怎么让二者挂钩呢?性价比。所以我们的贪心初解按照性价比排序,然后这题就做完了!然而我就是想不到,悲夫。

    完整地说一遍:先做初步转化,将重量为负数的物品先选上,则其权值变为 − 1 -1 1,即 “退回” 性价比为 − 1 w \frac{-1}{w} w1 。按照性价比排序,即先选择重量为 1 , 2 , … , m 1,2,\dots,m 1,2,,m 的物品,再 “退回” 重量为 − m , − ( m − 1 ) , … , − 1 -m,-(m{-}1),\dots,-1 m,(m1),,1 的物品。

    考虑最优解与其的差距。存在 balance \textit{balance} balance 方式,即总重量在 [ L − m , L + m ] [L{-}m,L{+}m] [Lm,L+m] 内摇摆;若经过若干次操作后,总重量不变,该替换会使得性价比变低(否则贪心初解可以更优),因此是无意义的。

    因此前缀和两两不同,即最多有 2 m 2m 2m 次调整。因此调整量不超过 2 m 2 2m^2 2m2,每个物品也只需考虑 2 m 2m 2m 以内的增减变化,拿出来做个部分背包即可。

    时间复杂度 O ( m 3 ) \mathcal O(m^3) O(m3),或者二进制分组 O ( m 3 log ⁡ m ) \mathcal O(m^3\log m) O(m3logm)

    代码

    #include 
    #include  // Almighty XJX yyds!!
    #include  // oracle: ZXY yydBUS!!!
    #include  // rainybunny root of the evil.
    #include 
    using llong = long long;
    # define rep(i,a,b) for(int i=(a); i<=(b); ++i)
    # define drep(i,a,b) for(int i=(a); i>=(b); --i)
    # define rep0(i,a,b) for(int i=(a); i!=(b); ++i)
    inline int readint(){
        int a = 0, c = getchar(), f = 1;
        for(; !isdigit(c); c=getchar()) if(c == '-') f = -f;
        for(; isdigit(c); c=getchar()) a = a*10+(c^48);
        return a*f;
    }
    
    const int MAXN = 300;
    llong posi[MAXN+1], nega[MAXN+1];
    
    const int MAXM = MAXN*(MAXN<<1);
    const llong INF = (1ll<<60)-1;
    llong dp[MAXM<<1|1];
    void knapsack(const int& cnt, int weight, int value){
        if(cnt == 0) return;
        for(int j=0,w,v; true; ++j){
            if((cnt>>j) == 1){
                w = weight*(cnt^(1<<j))+weight;
                v = value*(cnt^(1<<j))+value;
            }
            else w = weight<<j, v = value<<j;
            if(w < 0) rep(i,-w,MAXM<<1)
                dp[i+w] = std::max(dp[i+w],dp[i]+v);
            else drep(i,MAXM<<1,w) // positive
                dp[i] = std::max(dp[i],dp[i-w]+v);
            if((cnt>>j) == 1) break;
        }
    }
    
    inline int trim(const llong& v){
        return v >= (MAXN<<1) ? (MAXN<<1) : int(v);
    }
    int main(){
        int n = readint();
        llong L; scanf("%lld",&L);
        drep(i,n,1) scanf("%lld",&nega[i]);
        llong ans; scanf("%lld",&ans);
        rep(i,1,n) scanf("%lld",&posi[i]);
        if(L < 0){ // negate everything
            rep(i,1,n) std::swap(nega[i],posi[i]);
            L = -L;
        }
        rep(i,1,n) L += nega[i]*i;
        std::fill_n(dp,MAXM<<1|1,-INF);
        dp[MAXM] = 0; // known
        rep(i,1,n){ // greedily use them
            llong cnt = std::min(L/i,posi[i]);
            L -= i*cnt, ans += cnt;
            knapsack(trim(cnt),-i,-1);
            knapsack(trim(posi[i]-cnt),i,1);
        }
        drep(i,n,1){
            llong cnt = std::min(L/i,nega[i]);
            L -= i*cnt, ans += nega[i]-cnt;
            knapsack(trim(cnt),-i,1);
            knapsack(trim(nega[i]-cnt),i,-1);
        }
        if(L < MAXM && ans+dp[L+MAXM] >= 0)
            printf("%lld\n",ans+dp[L+MAXM]);
        else puts("impossible");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
  • 相关阅读:
    带你了解“JSON解析”
    9月28日复习
    玩转全志F1C200s 烧录 flash 镜像
    Kubernetes网络侃闲天
    操作系统原理,IO控制方式,轮询流程,中断驱动流程,设备IO部件演化;IO的软件组成与层次,设备独立性;IO相关技术,缓冲技术
    Unity3D 连接 SQLite 作为数据库基础功能【详细图文教程】
    【正则表达式总结】
    C/C++不同编译器对数组为0和void的处理
    Spring Boot整合swagger
    pikachu_php反序列化
  • 原文地址:https://blog.csdn.net/qq_42101694/article/details/126089661