P8392 [BalticOI 2022 Day1] Uplifting Excursion

P8392 [BalticOI 2022 Day1] Uplifting Excursion

题意

你有 \(2m+1\) 种物品,重量分别是 \([-m,m]\),每种物品有 \(a_i\) 个。问最多选择多少个物品使得总重量恰好是 \(l\)

\(m \le 300,|l| \le 10^{18}, a_i \le 10^{12}\)

思路

减少 DP 状态复杂度。

朴素多重背包,复杂度是 \(O(ml)\) 的。

注意到每个物品的价值都是 \(1\),且物品个数很少,且物品重量是区间 \([-m,m]\),考虑围绕这些特点思考。

众所周知选到恰好 \(l\) 的价值是不好做的,但是选到一个长度为 \(V=m\) 的价值区间是简单的,你随便选都能选到这样一个区间之内。

考虑到我们的物品种类和重量都很小,我们可以先选择尽可能多的物品使得总重量在区间 \([l-m,l]\) 之内。做法是先全部物品选上,如果总重量太大,就优先扔掉重量大的物品,否则就优先扔掉重量小的物品。

然后再做:退掉一些物品或者添加一些物品,使得总重量恰好是 \(s\),其中 \(s\le m\)

退掉一些物品,可以看做加入取反重量和价值的物品。因此问题是有 \(4m+2\) 种物品,重量在 \([-m,m]\) 范围内,价值 \(\in \{1,-1\}\),问填满 \(s \le m\) 大小的背包,最大价值。

背包要计算时要开多大呢?

我们目前选择的物品满足总质量在 \([l-m,l]\) 时价值最大。所以不会出现任何 DP 值比 \(0\) 大。我们要做的背包仅仅是找出最优的刚好凑到总质量是 \(l\) 的方法。

根据一点余数的分析,一个物品最多取 \(m\) 个,背包开 \(m^2\)

准确一点算,开 \(2 \cdot \frac{(m+1)(m)}{2}\),开大一点吧,开 \(2m^2\)。加上负的情况,一共开 \(4m^2\) 好了。

那么做多重背包,使用单调队列优化的话,总时间复杂度 \(O(m^3)\)

懒得写单调队列优化,写二进制拆分,复杂度 \(O(m^3\log a)\)。常数小,也能过。


2025.6.13

最近又做到这个题,然后感觉很眼熟啊,翻到这篇题解。拼尽全力翻出了写了一半的代码。

怎么能咕这么久的。

代码写一半就藏在文件夹深处不是一个好习惯。

算了还是写单调队列优化多重背包吧,学习一下也好。

code

不算难写吧。细节稍微有一点多。

#include<bits/stdc++.h>
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
namespace wing_heart {
    constexpr int N=303,inf=1e9+7;
    int n;
    ll l;
    ll m[N<<2];
    int w[N<<2],v[N<<2];
    int f[N*N<<2];
    int id(int x) { return x+n; };
    int iid(int x) { return x-n; };
    int reid(int x) { return x+((n<<1)|1); }
    struct pii {
        int x,y;
    };
    vector<pii> que[N];
    void main() {
        sf("%d%lld",&n,&l);
        rep(i,0,n<<1) sf("%lld",&m[i]), w[i]=iid(i), v[i]=1, w[reid(i)]=-w[i],v[reid(i)]=-1;
        ll sumw=0,sumv=0;
        rep(i,0,n<<1) sumw+=m[i]*w[i], sumv+=m[i];
        //[l-n+1,l]
        if(sumw>l) {
            for(int i=n;i>=1&&sumw>l;i--) {
                int p=id(i);
                ll k=min(m[p],(ll)ceil(1.0*(sumw-l)/w[p]));
                m[reid(p)]+=k, m[p]-=k;
                sumw-=k*w[p], sumv-=k;
            }
        }else {
            for(int i=-n;i<=-1&&sumw<l-n+1;i++) {
                int p=id(i);
                ll k=min(m[p],(sumw-l)/w[p]);
                m[reid(p)]+=k, m[p]-=k;
                sumw-=k*w[p], sumv-=k;
            }
        }
        if(sumw<l-n+1 || sumw>l) {
            puts("impossible");
            exit(0);
        }
        rep(i,0,n<<1) swap(m[i],m[reid(i)]);
        //多重背包
        int s=l-sumw;
        int t=2*n*n;
        rep(i,0,t<<1) f[i]=-inf;
        f[t]=0;
        rep(i,0,reid(n<<1)) {
            if(w[i]==0 || m[i]==0) continue;
            if(w[i]>0) {
                rep(j,0,w[i]-1) while(que[j].size()) que[j].clear();
                rep(j,0,t<<1) {
                    int p=(j-t)%w[i];
                    p=p<0?p+w[i]:p;
                    int c=j/w[i];
                    while(que[p].size() && que[p][0].y<(c-m[i])) que[p].pop_back();
                    if(f[j]!=-inf) {
                        int val=f[j]-c*v[i];
                        while(que[p].size() && que[p].back().x<=val) que[p].pop_back();
                        que[p].push_back({val,c});
                    }
                    if(que[p].size()) {
                        f[j]=max(f[j],que[p].front().x+(c*v[i]));
                    }
                }
            } else {
                rep(j,0,-w[i]-1) while(que[j].size()) que[j].clear();
                per(j,t<<1,0) {
                    int p=(j-t)%w[i];
                    p=p<0?p-w[i]:p;
                    int c=j/w[i];
                    while(que[p].size() && que[p][0].y<(c-m[i])) que[p].pop_back();
                    if(f[j]!=-inf) {
                        int val=f[j]-c*v[i];
                        while(que[p].size() && que[p].back().x<=val) que[p].pop_back();
                        que[p].push_back({val,c});
                    }
                    if(que[p].size()) {
                        f[j]=max(f[j],que[p].front().x+(c*v[i]));
                    }
                }
            }
        }
        if(f[s+t]==-inf) {
            puts("impossible");
            exit(0);
        }
        pf("%lld\n",sumv+f[s+t]);
    }
}
int main() {
    #ifdef LOCAL
    freopen("in.txt","r",stdin);
    freopen("my.out","w",stdout);
    #endif
    wing_heart :: main();
}
posted @ 2025-06-16 15:48  wing_heart  阅读(12)  评论(0)    收藏  举报