[BZOJ2287]消失之物(分治线段树/变形背包)

题目

有 n 个物品(\(n\leq 2000\)), 体积分别是 W1, W2, …, WN。第 i 个物品丢失了。 要使用剩下的 N – 1 物品装满容积为 x 的背包,有几种方法呢? 答案记为 Count(i, x) ,想要得到所有1 <= i <= N, 1 <= x <= M的 Count(i, x) 表格。

思路

直接暴力做:(\(n^3\))

1.线段树分治

\(n^3\)的算法进行分析可以发现,i=x , x+1 , x+2 . . . . m时,前面x-1个物品的加入情况是不变的,然而在这个暴力中却被重复计算了,于是考虑优化这一个重复计算。

于是采用(dfs)线段树的分治方法,先维护一个f数组做背包,向左儿子递归的时候将右儿子中的所有元素加入背包,反之亦然。当到叶子结点时就表示除了叶子节点这一个之外的所有元素都被加进了背包,即可输出答案。

用上面的方法,可以看出,在线段树每一层中,我们都将所有的数加了一遍,由于有logn层,所以总共加了nlogn次数,每加一个数是O(n)的,所以时间复杂度就是O(\(n^2logn\)),(或者可以理解成每个数在每一层都会且仅会被加一次),由于每一层创建了一个背包存储历史版本便于回溯,空间复杂度为O(\(nlogn\))

Code:

#include<bits/stdc++.h>
#define N 2005
#define mod 10
using namespace std;
int n,m;
int a[N],f[N];
void work(int l,int r)
{
    if(l==r)
    {
        for(int j=1;j<=m;++j) printf("%d",(f[j]%mod+mod)%mod);//先走左边所以i有序
        printf("\n");
        return;
    }
    int mid=(l+r)>>1;
    int tmp[N];
    memcpy(tmp,f,sizeof(f));
    for(int i=mid+1;i<=r;++i)
    for(int j=m;j>=a[i];--j) f[j]=(f[j]+f[j-a[i]])%mod;
    work(l,mid);
    memcpy(f,tmp,sizeof(f));
    for(int i=l;i<=mid;++i)
    for(int j=m;j>=a[i];--j) f[j]=(f[j]+f[j-a[i]])%mod;
    work(mid+1,r);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i) scanf("%d",&a[i]);
    f[0]=1;
    work(1,n);
    return 0;
}

2.背包

先做一次无损背包,对于每一个损失的i,在背包中减去它的贡献即可(

如果无损背包为f,那么

	memcpy(g,f,sizeof(f));
	for(int j=a[i];j<=m;++j) g[j]=(g[j]-g[j-a[i]])%mod;

g即为f减去i的贡献之后的背包

posted @ 2019-07-10 19:46  擅长平地摔的艾拉酱  阅读(203)  评论(0编辑  收藏  举报
/*取消选中*/