[冲刺国赛2022] 物品

一、题目

\(2n+1\) 种物品,重量分别为 \(-n,-n+1,...,n-1,n\),重量为 \(i\) 的物品有 \(a_i\) 个。你需要选出若干物品,在重量之和为 \(m\) 的基础上,最大化选取的数量。

\(1\leq n\leq 300,|m|\leq 10^{18},|a_i|\leq 10^{12}\)

二、解法

其实这题就是一个背包,只不过容量特别大。但是考虑到物品重量是很小的,可以按二进制位逐位考虑,就能让状态数限制在一个较小的范围内

首先把所有物品二进制拆分,拆分的普通形式是 2^0+2^1+2^2...+剩下的,因为要完全转化成二进制的形式,所以我们把剩下的那部分直接分解为二进制。然后把物品分配到对应的二进制位上,转化成 \(01\) 背包问题。

然后我们从低位到高位跑背包,设 \(dp[i][j]\) 表示现在考虑到了数位 \(i\),容量是 \(2^i\cdot j\) 的最大选取个数。到下一个数位时就把当前数位去掉。第二维可以限制在 \(O(n^2)\) 级别,时间复杂度 \(O(n^3\log |a|)\)

卡常小技巧:把有效状态限制在一个范围内,动态更新这个范围。

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
const int M = 605;
#define int long long
const int N = 150000;
const int inf = 1e18;
#define pb push_back
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,a[M],dp[2][2*N+5];vector<int> v[M];
signed main()
{
    freopen("goods.in","r",stdin);
    freopen("goods.out","w",stdout);
    n=read();m=read();
    for(int i=0;i<=(n<<1);i++)
    {
        a[i]=read();
        if(i==n) continue;
        for(int j=0;j<=40;j++) if((1ll<<j)<=a[i])
            v[j].pb(i-n),a[i]-=(1ll<<j);
        for(int j=40;j>=0;j--) if((1ll<<j)<=a[i])
            v[j].pb(i-n),a[i]-=(1ll<<j);
    }
    int e=0,t=1,L=N,R=N;dp[0][N]=inf;
    //都增加inf,这样0就相当于-inf
    for(int i=0;i<=40;i++)
    {
        for(int d:v[i])
        {
            e^=1;t^=1;
            int tl=min(L,max(0ll,L+d));
            int tr=max(R,min(2*N,R+d));
            for(int j=L;j<=R;j++) dp[e][j]=dp[t][j];
            for(int j=tl;j<L;j++) dp[e][j]=0;
            for(int j=R+1;j<=tr;j++) dp[e][j]=0;
            for(int j=L,k;j<=R;j++) if((k=j+d)>=0 && k<=2*N)
                dp[e][k]=max(dp[e][k],dp[t][j]+(1ll<<i));
            L=tl;R=tr;
        }
        e^=1;t^=1;
        for(int j=L;j<=R;j++) dp[e][j]=0;
        int tl=N,tr=N;
        for(int j=L,k;j<=R;j++) if((j&1)==(m>>i&1))
        {
            k=N+(j-N>>1);
            dp[e][k]=max(dp[e][k],dp[t][j]);
            tl=min(tl,k);tr=max(tr,k);
        }
        L=tl;R=tr;
    }
    if((m>>41)+N<L || (m>>41)+M>R || dp[e][(m>>41)+N]<inf)
        {puts("impossible");return 0;}
    printf("%lld\n",dp[e][(m>>41)+N]-inf+a[n]);
}
posted @ 2022-07-31 16:13  C202044zxy  阅读(261)  评论(0编辑  收藏  举报