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