zoukankan      html  css  js  c++  java
  • 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
    ",sum);
    65     ref(i,1,m)if(ans[i])printf("%d ",i);
    66     printf("
    ");
    67 }
  • 相关阅读:
    WinForm的Chart控件画条形图
    WinForm的Chart控件画折线图
    自定义控件
    左侧收缩菜单
    数组
    C#生成随机数的三种方法
    WinForm之GDI手动双缓冲技术
    WinForm之GDI画图步骤
    WinForm GDI编程:Graphics画布类
    翻译:《实用的Python编程》08_02_Logging
  • 原文地址:https://www.cnblogs.com/Blog-of-Eden/p/7743771.html
Copyright © 2011-2022 走看看