2019.12.6 0:09
题目链接:https://www.luogu.com.cn/problem/P1036
这个题用到了搜索与回溯的算法,所以先复习一下最简单的搜索与回溯:
搜索与回溯算法的框架:
第一种写法:
int Search(int k)
{
for (i=1;i<=算符种数;i++)
if (满足条件)
{
保存结果
if (到目的地) 输出解;
else Search(k+1);
恢复:保存结果之前的状态{回溯一步}
}
}
第二种写法:
int Search(int k)
{
if (到目的地) 输出解;
else
for (i=1;i<=算符种数;i++)
if (满足条件)
{
保存结果;
Search(k+1);
恢复:保存结果之前的状态{回溯一步}
}
}
以一个最简单的搜索回溯题来练一下手:
【题目描述】设有n个整数的集合{1,2,…,n},从中取出任意r个数进行排列(r<n),试列出所有的排列。
解法:
主函数外定义全局变量(静态区变量自动初始化为0)
int n,r;
int a[10001]; //存储排列结果
bool rec[10001];//记录数值是否使用过,rec[num]==1代表数num已经使用过了
再写一个函数用来输出正确的排列
int print()
{
for (int i=1;i<=r;i++)
cout<<setw(3)<<a[i];
cout<<endl;
}
最后是重头戏,此题的核心代码
int search(int step)
{
int i;
for (i=1;i<=n;i++)
if (rec[i]==0) //判断i是否可用
{
a[step]=i; //保存结果
rec[i]=1;
if (step==r) print();
else search(step+1);
rec[i]=0;
}
}
最后在主函数内调用
int main()
{
cin>>n>>r;
search(1);
}
P1036选数这个题跟全排列非常类似,不同的是此题全排列的不是1…n,而是所给的每一个整数。
而且在排列完成后还要判断排列结果的和是否为素数。
所以先上一个判断素数的函数:
int is_prime(long long int num)
{
int i;
for(i=2;i<=sqrt(num);i++)
{
if(num%i==0)
{
return 0;
}
}
return 1;
}
之后在全局定义变量,免去了赋0的时间
long long int g[21],sum;//g存储所给的整数,sum用来保存全排列的和
int rec[21];//记录元素是否被使用
int k,n,ans;//ans用来记录符合条件的全排列
接下来是最关键的函数
void search(int step,int temp)
{
int i;
//在这里i从temp开始,而不是从0开始,因为1+2+3与1+3+2是一样的(举个栗子而已)
for(i=temp;i<n;i++)
{
if(rec[i]==0)
{
rec[i]=1;
sum+=g[i]; //保存当前结果
if(step==k) //如果已经排了k个数,就判断sum是否为素数
{
if(is_prime(sum)) ans++;
}
else search(step+1,i+1); //否则就排下一个数
//回溯后记录要归0,sum要减去当前元素
rec[i]=0;
sum-=g[i];
}
}
}
综上所述,最终题解为
#include<iostream>
#include<cmath>
using namespace std;
long long int g[21],sum;
int rec[21];
int k,n,ans;
int is_prime(long long int num)
{
int i;
for(i=2;i<=sqrt(num);i++)
{
if(num%i==0)
{
return 0;
}
}
return 1;
}
void search(int step,int temp)
{
int i;
for(i=temp;i<n;i++)
{
if(rec[i]==0)
{
rec[i]=1;
sum+=g[i];
if(step==k)
{
if(is_prime(sum)) ans++;
}
else search(step+1,i+1);
rec[i]=0;
sum-=g[i];
}
}
}
int main()
{
int i,j;
cin >> n >> k;
for(i=0;i<n;i++) scanf("%lld",&g[i]);
search(1,0);
cout << ans;
return 0;
}