luogu4241 采摘毒瘤 题解

题意分析

给出 $n$ 种物品,每种物品有数量和大小,要求放入一定大小的背包,求不能再放入任何物品的方案数。

思路分析

分析后可以发现这个问题就是在多重背包计数问题的基础上加上了放不进剩下的物品的条件,考虑对这个条件进行处理。

可以想到枚举剩下的物品中体积最小的物品,然后将比它小的物品先放进去,再对剩下的物品做多重背包。每次都重新计算显然复杂度太大,想到先将物品按照体积从大到小排序,然后一种种放入即可。

暴力多重背包显然是过不去的,想到用类似于单调队列优化多重背包的思想。但是这里是计数问题,直接用队列就可以了。发现求解时不需要知道队列内具体的元素,只需要知道队列内的元素和就可以,因此可以不进行队列实际操作。由于计算时需要用到上一次的状态,因此需要多开一维,可以使用滚动数组优化。答案就是 $sum_{j=max(m-sum-d+1,0)}^{m-sum} f[j]$,$sum$ 是比当前枚举到的物品小的物品大小之和。

注意,因为当前枚举的物品一定要剩下,所以再计算的时候先将当前物品个数减 $1$ ,计算结束后再用原个数更新状态。

因为计算的情况一定有物品剩余,因此要特判所有物品都放得下的情况。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
const int N=600,M=1e5+100,P=19260817;
struct Node
{
    int k,d;
    #define k(i) a[i].k
    #define d(i) a[i].d
}a[N];
int n,m,sum,ans,q;
int f[2][M];
bool cmp(Node x,Node y)
{
    return x.d>y.d;
}//按大小从大到小排序
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&k(i),&d(i)),sum+=k(i)*d(i);
    if(sum<=m)
    {
        puts("1");
        return 0;
    }//特判
    sort(a+1,a+n+1,cmp);f[0][0]=1;//初值
    for(int i=1;i<=n;i++)
    {
        sum-=k(i)*d(i);k(i)--;q^=1;//数量先减一
        for(int u=0;u<d(i);u++)
        {
            int maxp=(m-u)/d(i),now=0;
            for(int p=0;p<=maxp;p++)
            {
                if(p-k(i)-1>=0)
                    now=(now+P-f[q^1][u+(p-k(i)-1)*d(i)])%P;//去掉末尾过时的部分
                f[q][u+p*d(i)]=now=(f[q^1][u+p*d(i)]+now)%P;//计算并更新状态
            }
        }
        for(int j=max(m-sum-d(i)+1,0);j<=m-sum;j++)
            ans=(ans+f[q][j])%P;k(i)++;//计算答案并加回来
        for(int u=0;u<d(i);u++)
        {
            int maxp=(m-u)/d(i),now=0;
            for(int p=0;p<=maxp;p++)
            {
                if(p-k(i)-1>=0)
                    now=(now+P-f[q^1][u+(p-k(i)-1)*d(i)])%P;
                f[q][u+p*d(i)]=now=(f[q^1][u+p*d(i)]+now)%P;
            }
        }//更新状态
    }
    printf("%d",ans);
    return 0;
}
原文地址:https://www.cnblogs.com/TEoS/p/13616349.html