选数(NOIP2002) 搜索与回溯算法



选数(NOIP2002) 搜索与回溯算法。

 [问题描述]:

  已知 n 个整数 x1,x2,…,xn,以及一个整数 kkn)。从 n 个整数中任选 k 个整数相加,可分别得到一系列的和。例如当 n=4k34 个整数分别为 371219 时,可得全部的组合与它们的和为:

    3712=22  371929  7121938  3121934

  现在,要求你计算出和为素数共有多少种。

  例如上例,只有一种的和为素数:371929)。

[输入]

  键盘输入,格式为:

  n , k 1<=n<=20kn

  x1,x2…,xn 1<=xi<=5000000

[输出]

  屏幕输出,格式为:

  一个整数(满足条件的种数)。

[输入输出样例]:

  输入:

   4 3

   3 7 12 19

  输出:

   1

 

问题分析:

本题可以分解成以下两部分:

1.  n个数中任取k的组合求法;

2.  判断一个整数是否是素数。

我们就这两个部分分别进行讨论。

1)从n个数中任取k个数的组合,即C(n,k),(k<n)

     由于题目给定 n<=20,因此可以通过搜索实现。穷举和深度优先搜索dfs是通常采用的两种方法。

 穷举

 用数组a[1],a[2],….a[k]记录每种组合中各数的下标。a[0]为结束条件。

 初值:      1           2     。。。。。 k-1             k

 下一个: 1           2     。。。。。k-1             k+1

        …..         …    …    ……            ….

 直到:     1           2     。。。。。k-1             n

        。。。       。。。       。。。。            。。。

最后一个组合:

               n-k+1   n-k+2 …..              n-1            n 

算法描述:

while(a[0]==0)

         {

求和;

判断素数;

             j=k;

             while(a[j]==n-k+j)

              j–;

            a[j]=a[j]+1;

            for(i=j+1;i<=k;i++)

             a[i]=a[i-1]+1;    

         }      

当选用前面的输入样例,n=4,k=3时,a[1]~a[3]的变化如下:

a[0]               a[1]               a[2]               a[3]               生成的组合

0                       1                   2                    3                  3  7  12

0                       1                   2                    4                  3  7  19 

0                       1                   3                    4                  3  12  19

0                       2                   3                    4                  7  12  19

1                       2                   3                    4                  循环结束

 

 

简单dfs

 可用递归搜索来实现(用S表示各组合中的数据之和,用x[i]记录各原始数据)

void search(dep)  //设定第dep个数据的值

 {

     for(i=a[dep-1]+1;i<=n-k+dep;i++)

      {

          a[dep]=i;

          s+=x[i];

          if(dep<k)  search(dep+1);

          else 加和并判断素数

      }   

 }  

 

(2)判断一个整数是否为素数

本题的数据范围比较大,快速判断一个整数为素数则是提高速度的关键。这里我采用筛法求素数(详见拙作http://blog.csdn.net/fisher_jiang/archive/2006/05/11/724956.aspx),至于素数测试的概率算法,离作以后研究吧。

素数的筛法对于一个大整数X,只要知道不超过   sqrt(X)的所有素数,划去所有p的倍数2p,3p,…剩下的整数就是不超过X的全部素数

 

我们这里设y=int(sqrt(s))+1,.s为待判断的整数。如果在2y中不存在素数is的约数,则 s 为素数。由于xi<=5000000,n<=20,因此s<=100000000,y<=10000.210000之间共有1229个素数,如果预先将这1229个数据存储到素数集合中,超时的可能性大大降低。

 

/*f[10001];//存储110000之间数据的状态,f[i]=1表示i为素数,0表示i为非素数*/

void Get_Prime()//筛选法求110000之间的素数

 {

     memset(p,0,sizeof(p));

     int i,j,s;

     s=0;

     f[1]=0;

     for(i=2;i<=10000;i++)

       f[i]=1;

     for(i=2;i<=10000;i++)

      if(f[i]==1)

       {


           s++;

           p[s]=i;

           j=2*i;

           while(j<=10000)

            {

                f[j]=0;

                j+=i;

            }   

       }     

 } 

 

下面是采用穷举法的例程:

#include<iostream>

#include<cstring>

using namespace std;

int p[1230]; //存储10000以内的素数

long x[21];//存储原始数列

int a[21];//存储k个数的选择状态:a[i]=j表示选择的第i个数在原始数列中的下标为j

long sum;//累加器,统计k个数的和

long total;//统计满足要求的组合个数

int f[10001];//存储110000之间数据的状态,f[i]=1表示i为素数,0表示i为非素数

int n,k;

void Get_Prime()//筛选法求110000之间的素数

 {

     memset(p,0,sizeof(p));

     int i,j,s;

     s=0;

     f[1]=0;

     for(i=2;i<=10000;i++)

       f[i]=1;

     for(i=2;i<=10000;i++)

      if(f[i]==1)

       {

           s++;

           p[s]=i;

           j=2*i;

           while(j<=10000)

            {

                f[j]=0;

                j+=i;

            }   

       }     

 } 

bool fit(long sum)

{

    int m,Max;

    Max=int(sqrt(sum+0.0))+1;//开始没有+1,测试数据木有通过

    m=1;

    while(sum%p[m]!=0&&p[m]<Max)

     m++;

    if(p[m]>=Max)

     return 1;

    else

     return 0;  

}       

int main()

{  

 

    int i,j;

    Get_Prime();

    while(cin>>n>>k)

     {

         for(i=1;i<=n;i++)

          cin>>x[i];

        total=0;

        for(i=0;i<=k;i++)

         a[i]=i;

        while(a[0]==0)

         {

             sum=0;

             for(i=1;i<=k;i++)

               sum+=x[a[i]];

             if(fit(sum))

                total++;

             j=k;

             while(a[j]==n-k+j)

              j–;

            a[j]=a[j]+1;

            for(i=j+1;i<=k;i++)

             a[i]=a[i-1]+1;    

         }      

      cout<<total<<endl;  

     }   

    return 0;

}    

 http://blog.csdn.net/fisher_jiang/article/details/953834