一、素数环问题
找出长为n的,由1,2,…,n组成的一个环,环中所有相邻的数的和都为素数的环个数。(注意,如1,2,3,4和2,3,4,1是同一个环,只计算一次,但是1,2,3,4和1,4,3,2却不算是同一个环)
- 解空间:对于该问题来说 ( a 1 , a 2 , . . . , a n ) (a_1,a_2,...,a_n) (a1,a2,...,an)为问题的解空间,该问题求有多少个解。所以只要遍历解空间即可。
- 约束条件:
- 相邻数和为素数2
- 首位数和为素数
- 首位为1
采用回溯法解决问题,显然由于数不能重复,定义一个visit数组存储是否已经访问过的信息即可。
#include<iostream>
using namespace std;
int visit[20]={0};
int n;
int count=0;
int isprime(int n){
if(n<2||(n>2&&n%2==0)) return 0;
for(int i=3;i*i<=n;i+=2){
if(n%i==0) return 0;
}
return 1;
}
void dfs(int k,int cnt)//k表示当前数,cnt 表示累计数
{
if(cnt==n&&isprime(k+1))//最后一个数和第一个数和是素数
{
count++;
return;
}
for(int i=2;i<=n;i++){
if(!visit[i]&&isprime(k+i))//当前数和下一个数是素数
{
visit[i]=1;
dfs(i,cnt+1);
visit[i]=0;
}
}
}
int main(){
cin>>n;
visit[1]=1;
dfs(1,1);
cout<<count;
return 0;
}
时间复杂度,emmm,怎么说也有 O ( n ! ) O(n!) O(n!)吧,难顶
二、分糖果
菜菜最近上网课,非常无聊,天天在家和一群小朋友玩,但是菜菜是个不好玩的人,小朋友都不愿意和他玩,菜菜最近想到了一个好办法,他想给小朋友买糖果,但是小朋友对每种糖果的需求是不同的,也就是说,每个小朋友的口味不同,他想在满足小朋友的同时,使得自己买的糖果数量最少,但是同时不能买相同的糖果,因为这样小朋友会觉得糖果一样的,没什么意思,菜菜是个穷鬼,你能写个程序帮助他使得他买的糖果数量最少吗?
这题用回溯法也好解决,先定义一个矩阵a[i][j],代表i个孩子对j号糖果的需求。然后每层选一个进行试探(注:不能重复选一个列)
#include<iostream>
#define INF 10000000
using namespace std;
int a[12][12];
int visit[12]={0};
int minx = INF;
int n;
void dfs(int i,int count){
if(i==n){
if(minx>count)
minx = count;
return;
}
for(int j=0;j<n;j++)
if(visit[j]==0){
visit[j]=1;
dfs(i+1,count+a[i][j]);
visit[j]=0;
}
}
int main(){
cin>>n;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
cin>>a[i][j];
dfs(0,0);
cout<<minx<<endl;
return 0;
}
时间复杂度还是 O ( n ! ) O(n!) O(n!),暂时还没想到是否可以优化。有想到的可以@我 hhh
三、假·完美数
我们将一个十进制正整数N定义为完美数当且仅当 任意两个相邻的数位的 差的绝对值不超过1.形式化定义为: 对于 N = d1d2d3d4…dm. 对于 ∀i∈[1 , m) 有 |di - d(i + 1)| <= 1.
例如: 111 . 777 . 123. 322 都是完美数,而 115 . 224. 124 都不是完美数.
现在想知道,第K小完美数是多少.(1 <= k <= 100000)
这个题用回溯法解决,其实这个题回溯解决还需要知道几个条件,我也是后来才知道的,确定条件是最大的数有多少位,对于本题来说第十万小的数实际上已经到了10位数了,无疑int是装不下的
回溯法
- 对于第i数位x来说,为保持完美数状态,必须满足i+1位数在x-1,x,x+1。
- 边界条件:除了第1数位不能取到零外,其余数位的范围均在 0 < = x < = 9 0<=x<=9 0<=x<=9
实际上回溯的思路就是搜索出所有可能的值,然后排序。emmm,有些无脑。没什么好讲的,源代码如下
#include<cstdio>
#include<iostream>
#include <algorithm>
using namespace std;
long long b[200000]={0};
int c=1; //计数,目前初始化为1
void dfs(long long n,int k){
if(k==0) return;
b[c++]=n;//记录当前值
int m=n%10;//取当前数位
for(int j=m-1;j<=m+1;j++){
if(j>=0&&j<=9)
dfs(n*10+j,k-1);
}
}
int main()
{
//初始值1~9的回溯
for(int i=1;i<=9;i++){
dfs(i,10);
}
sort(b+1,b+c);//将搜索结果进行排序
int n,k;
scanf("%d",&n);
while(n--){
scanf("%d",&k);
printf("%lld\n",b[k]);
}
return 0;
}
对于回溯算法不停的调用递归函数,然后将得出的结果储存起来,时间大头花排序上,在时间效率上还是有点低下。还有一种方法也可以做出此题,大思路都是一样的,直接列出所有结果,然后直接读答案,区别大概在于不需要回溯,也不需要排序,因为计算过程中结果已经排好了。
优化算法
对于该假完美数来说,第kn位数的循序必定依照n-1位的顺序构建,例如
- 1,2,3,4,5,6,7,8,9
- 10,11,20,21,22,32,33,34…
- 100,101,110,111,…
现在很明显了,对于首位1的2位完美数,比首位2的2位完美数都要小。这时候,我们只需要依次将完美数填入即可。
首先构建一个完美数表
[
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
]
[1,2,3,4,5,6,7,8,9]
[1,2,3,4,5,6,7,8,9],然后将1的2位填入,
[
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
,
11
]
[1,2,3,4,5,6,7,8,9,10,11]
[1,2,3,4,5,6,7,8,9,10,11],2的2位填入
[
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
,
11
,
21
,
22
,
23
]
[1,2,3,4,5,6,7,8,9,10,11,21,22,23]
[1,2,3,4,5,6,7,8,9,10,11,21,22,23],依次类推,即可
代码如下.
#include<stdio.h>
long long a[100001]={0,1,2,3,4,5,6,7,8,9};
int q=1,p=10;//队首,队尾
int n,k;
int main()
{
while(p!=100001){//队尾已经符合要求
int m=a[q]%10;//取当前数位
for(int i=m-1;i<=m+1;i++){
if(i>=0&&i<=9){
a[p++]=a[q]*10+i;
}
}
q++;
}
scanf("%d",&n);
while(n--){
scanf("%d",&k);
printf("%ld\n",a[k]);
}
return 0;
}
时间复杂度
O
(
n
)
O(n)
O(n),