Codeforces 286E Ladies' Shop

大致题意:

让你构造尽量少的数,

使得从这些数中, 选出若干个数(可以重复选同一个数,和<=m)它们的和组成的集合,

与输入的n个数的集合完全相同

n,m<=1e6:

 

本题做法:

首先我们能轻易得出一个结论,

构造的这些数,一定出现在给定的n个数之中。

这个结论是显然的,这里不再赘述。

于是我们得到一个新的题意:

从n个数中选出若干个数,使得它们能够组成的和的集合与原序列完全相同。

以下两个算法都基于这个结论:

算法1(来自cwy的一个tle做法):

考虑贪心。从小到大枚举输入的n个数,

如果当前数无法用之前选中的数的和来表示,

那么答案中一定包含这个数,即我们需要选中这个数。

那么,我们要维护一个背包,维护当前已选中的数能够构成的<=m的和。

时间复杂度nm。

算法2:

我们考虑,由于要从这n个数中选择若干个数,

那么未被选择的数,一定能用被选择的数的和来表示,所以才能被删去。

我们来考虑一种做法:

对于原序列中任意一个a,b(a和b可以相等),

若a+b存在,那么a+b应当被删去。

若a+b<=m,但a+b不存在,那么无解(这是显然的)

我们来证明该做法的正确性。

无解的情况很好判断,以下证明的前提是该序列有解。

那么有解的前提是,对于序列中任意一个a,b,若a+b<=m,那么a+b存在于序列中(这是显然的)

如果对于原序列中一个数s(s<=m),该数能够用序列中某一些数的和来表示,

设s=a0+a1+a2+....+an=a0+(a1+a2+..+an) 。由于有解,a0, a1+a2+..+an也存在于序列中。

那么该数就能够用序列中某两个数的和来表示。

所以判断s是否应当被删去的方法是,判断s是否能表示成a+b,使得a,b存在于序列中。

说了这么多,感觉这个结论还是很显然。

那么关键来了,怎么实现这个算法?

我们需要把原序列中任意两个数的和全部标记起来,然后一一从原序列中删去。

这就是fft模板题了吧。

时间复杂度mlogm

 

代码:

 1 #include <cmath>
 2 #include <cstdio>
 3 #include <complex>
 4 #include <iostream>
 5 using namespace std;
 6 #define ref(i,x,y)for(int i=x;i<=y;++i)
 7 int read()
 8 {
 9     char c=getchar();int d=0,f=1;
10     for(;c<'0'||c>'9';c=getchar())if(c=='-')f=-1;
11     for(;c>='0'&&c<='9';d=d*10+c-48,c=getchar());
12     return d*f;
13 }
14 const int N=1<<21;
15 const double pi=acos(-1);
16 int rev[N],sz,SZ,n,m,a[N];
17 bool ans[N];
18 typedef complex<double> com;
19 class array{
20 public:
21     com a[N];
22     void fft(int tp)
23     {
24         ref(i,0,SZ-1)if(rev[i]<i)swap(a[rev[i]],a[i]);
25         for(int i=2;i<=SZ;i<<=1)
26         {
27             int I=i>>1;com w(cos(pi/I),tp*sin(pi/I));
28             for(int j=0;j<SZ;j+=i)
29             {
30                 com W(1,0);
31                 for(int k=j;k<j+I;++k,W=W*w)
32                 {
33                     com A=a[k],B=a[k+I];
34                     a[k]=A+B*W;a[k+I]=A-B*W;
35                 }
36             }
37         }
38         if(tp==-1)ref(i,0,SZ-1)a[i]/=SZ;
39     }
40 }A;
41 int main()
42 {
43     n=read(),m=read();
44     ref(i,1,n)a[i]=read();
45     while((1<<sz)<m)++sz;
46     ++sz;SZ=1<<sz;
47     ref(i,0,SZ-1)rev[i]=(rev[i>>1]>>1)|((i&1)<<(sz-1));
48     ref(i,1,n)A.a[a[i]]=com(1,0),ans[a[i]]=1;
49     A.fft(1);
50     ref(i,0,SZ-1)A.a[i]=A.a[i]*A.a[i];
51     A.fft(-1);
52     ref(i,1,m)if((int)(A.a[i].real()+0.5)>=1)
53     {
54         if(!ans[i])
55         {
56             cout<<"NO"<<endl;
57             return 0;
58         }
59         ans[i]=0;
60     }
61     cout<<"YES"<<endl;
62     int sum=0;
63     ref(i,1,m)if(ans[i])sum++;
64     printf("%d\n",sum);
65     ref(i,1,m)if(ans[i])printf("%d ",i);
66     printf("\n");
67 }

 

转载于:https://www.cnblogs.com/Blog-of-Eden/p/7743771.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值