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;
}