洛谷之选数
洛谷试题之选数
首先分析题目,从n个数里选m个,输出相加为素数的个数。
由数学中的排列组合可知,有\(C^m_n\), 我们需要列举出所有可能性,这时候我们就可以想到用搜索。
但是还有一个问题 比如1 3 7是和是素数,但是还有五种情况,1 7 3;3 7 1,3 1 7,7 1 3,7 3 1
但是我们只需要输出一个就行了,我个人有两种解决办法。
第一种就是利用排列组合知识,假设有要选出m个数,有\(A^m_n\)种方式,即将m的阶乘。
代码如下:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<queue>
#include<stack>
#include<algorithm>
#include<vector>
#include<map>
#define MAXN 0x3f3f3f3f
using namespace std;
int n, m;
int counts =0;
int a[1000];
bool vis[1000];
int jc(int x)//计算阶乘
{
int h=1;
for (int i = 1; i <= x;i++)
h *= i;
return h;
}
void dfs(int x,int t)//开搜
{
if(x==m)//如果已经搜了有m个数
{
if(t==1||t==0)
{
s=1;
}
int s = 0;
for (int i = 2; i < sqrt(t);i++)//判断是不是素数(质数)
{
if(t%i==0)
{
s = 1;
break;//如果不是,标记s为1
}
}
if(s!=1)
{
counts++;
}
return;
}
else{
for (int i = 1; i <= n;i++)
{
if(!vis[i])//如果vis[i]没有搜过
{
vis[i] = 1;//标记vis[i]为1;
t += a[i];//求和
dfs(x + 1, t);//搜下一个数
vis[i] = 0;//回溯
t -= a[i];
}
}
}
}
int main()
{
memset(vis, 0, sizeof(vis));
cin >> n >> m;
for (int i = 1; i <= n;i++)
{
cin >> a[i];
}
dfs(0, 0);
cout << counts/(jc(m));//除jie去重复的数目,即m的阶乘
return 0;
}
还有一种方法
我们可以思考下出现重复的原因
拿数据 4 3
3 7 12 19来说
首先 我们从3 开始搜
3 7 12
3 7 19
3 12 7
3 12 19
3 19 7
3 19 12
之所以出现重复 是因为每次我们标记的都是一个点,
如果我们标记小于等于某个点的所有点
那么就不会出现重复的情况。
因此,我们数组里的数据必须是从小到大,样例虽然是从小到大的,但是为了保险起见,还是用sort对数组排一下序。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<queue>
#include<stack>
#include<algorithm>
#include<vector>
#include<map>
#define MAXN 0x3f3f3f3f
using namespace std;
int n, m;
int counts =0;
int a[100000];
bool vis[100000];
/*int jc(int x)
{
int h=1;
for (int i = 1; i <= x;i++)
h *= i;
return h;
}*/
void dfs(int x,int t)
{
if(x==m)
{
if(t==1||t==0)
{
s=1
}
int s = 0;
for (int i = 2; i < sqrt(t);i++)
{
if(t%i==0)
{
s = 1;
break;
}
}
if(s!=1)
{
counts++;
}
return;
}
else{
for (int i = 1; i <= n;i++)
{
if(!vis[i])
{
for (int j = 1; j <= i;j++)
{vis[j] = 1;
}//标记比i小的所有点 包括i
t += a[i];
dfs(x + 1, t);
for (int j = 1; j <= i;j++)
{vis[j] = 0;
}//回溯
t -= a[i];}
}
}
}
int main()
{
memset(vis, 0, sizeof(vis));
cin >> n >> m;
for (int i = 1; i <= n;i++)
{
cin >> a[i];
}
sort(a + 1, a+n);//sort排序 以防万一顺序顺序不是从小到大
dfs(0, 0);
cout << counts;
return 0;
}
经过为神(为神永远滴神)的指点后,我了解到了另一种解决重复的办法
那就是每搜完一次,就标记上次搜的点,然后从标记的点+1开始搜。
代码如下
#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<queue>
#include<stack>
#include<algorithm>
#include<vector>
#include<map>
#include<cmath>
#define MAXN 0x3f3f3f3f
using namespace std;
int n, m;
int counts =0;
int a[100000];
/*int jc(int x)
{
int h=1;
for (int i = 1; i <= x;i++)
h *= i;
return h;
}*/
void dfs(int x,int t,int start)
{
if(x==m)
{
int s = 0;
for (int i = 2; i <sqrt(t);i++)
{
if(t%i==0)
{
s = 1;
break;
}
}
if(s!=1)
{
counts++;
}
return;
}
else{
for (int i = start; i <= n;i++)
{
dfs(x + 1, t +a[i], i + 1);//这里将t+=a[i]变成t+a[i]写入下一个搜索函数里,可以不用回溯。start是为了标记位置,防止重复/
}
}
}
int main()
{
memset(vis, 0, sizeof(vis));
cin >> n >> m;
for (int i = 1; i <= n;i++)
{
cin >> a[i];
}
sort(a + 1, a+n);
dfs(0, 0,1);
cout << counts;
return 0;
}