xsy 1836 - Shop

from NOIP2016模拟题36

Description

商店里有n种背包和m种物品,物品体积为1到m,背包容积<=m
给出n个背包的容积
现在要求出这样一个物品集合,满足:
1)对于任意一个背包,都能找到这样一个物品的子集,使得这个子集中物品的体积和(每个物品可以使用多次)恰好等于背包的容积

2)对于每一个体积和小于等于m的物品子集(每个物品可以使用多次),都有一个背包容积恰好等于这个体积和

求满足条件的最少的物品集合

Analysis

有解 \(\Leftrightarrow\) 物品集=背包体积集合 时 有解
然后我们要缩小集合
就是判断一个数能不能被小于它的数背包dp出来
然而由于题目的特殊性质
只用是否存在两个小于它的数a,b满足a+b等于它
我们可以用FFT
简单证明:
若已经求出有解
a,b,c,d在物品集中
则2a,3a.....都在物品集中
同理a+b,2a+b,4a+2b+3c,2a+4b+5c+d什么的都在集合中
集合中的数任意两个相加即可表示任何数

证完了
回到一开始怎么知道物品集=背包集的情况是否满足条件?
将物品集conv后看看是否出现新数
有就不满足

Code

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <algorithm>
using namespace std;
const double pi=acos(-1.0);
const int M=1000007;
const int N=2097152;
 
inline int rd(){
    int x=0;bool f=1;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
    for(;isdigit(c);c=getchar()) x=x*10+c-48;
    return f?x:-x;
}
 
int n,m;
int rev[N];
struct CP{
    double x,i;
    CP(double xx=0.0,double ii=0.0){x=xx;i=ii;}
}a[N];
CP operator +(CP x,CP y){return CP(x.x+y.x,x.i+y.i);}
CP operator -(CP x,CP y){return CP(x.x-y.x,x.i-y.i);}
CP operator *(CP x,CP y){return CP(x.x*y.x-x.i*y.i,x.i*y.x+x.x*y.i);}
 
void FFT(CP *a,int fl){
    int i,j,k;
    CP W,Wn,u,v;
    for(i=0;i<N;i++) if(rev[i]<i) swap(a[i],a[rev[i]]);
     
    for(i=2;i<=N;i<<=1){
        Wn=CP(cos(2*pi/i),fl*sin(2*pi/i));
        for(j=0;j<N;j+=i){
            W=CP(1,0);
            for(k=j;k<j+i/2;k++,W=W*Wn){
                u=a[k];
                v=W*a[k+i/2];
                a[k]=u+v;
                a[k+i/2]=u-v;
            }
        }
    }
     
    if(fl==-1)
        for(i=0;i<N;i++) a[i].x=(a[i].x/N)+0.5;
}
 
int vis[M];
int ans=0;
 
int main(){
    int i,j,k,x;
    n=rd(),m=rd();
    for(i=1;i<=n;i++){
        x=rd();
        vis[x]=1;
        a[x]=CP(1,0);
    }
     
    for(i=0;i<N;i++) rev[i]=(rev[i>>1]>>1)|((i&1)?(N>>1):0);
    FFT(a,1);
    for(i=0;i<N;i++) a[i]=a[i]*a[i];
    FFT(a,-1);
     
    ans=n;
    for(i=1;i<=m;i++)
    if(a[i].x>=1){ //注意现在是double 
        if(!vis[i]){
            puts("NO");
            return 0;
        }
        ans--;
    }
     
    puts("YES");
    printf("%d\n",ans);
    for(i=1;i<=m;i++)
    if(vis[i]&&a[i].x<1){
        if(ans>1) printf("%d ",i);
        else printf("%d\n",i);
        ans--;
    }
    return 0;
}
posted @ 2017-02-04 17:56  _zwl  阅读(114)  评论(0编辑  收藏  举报