2021-01-20 总结
今天的课收获颇多,对于我这么一个搜索学的一知半解的人来说,这样一节搜索的强化课对我实在是太有帮助了,虽然现在离精通还有很远,但是至少我已经能独立写出一些思维难度比较简单的搜索题了。总之,今天真是收获满满。
1.八皇后
题目描述 一个如下的 6×6的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。
上面的布局可以用序列 2 4 6 1 3 5 来描述,第 i 个数字表示在第 i 行的相应位置有一个棋子,如下: 行号 1 2 3 4 5 6
列号 2 4 6 1 3 5这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。 并把它们以上面的序列方法输出,解按字典顺序排列。 请输出前 3个解。最后一行是解的总个数。
输入格式
一行一个正整数 n,表示棋盘是 n×n 大小的。输出格式
前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。输入输出样例
输入
6
输出
2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4
八皇后的原题我在网上没找到,只好拿 n 皇后的题来凑合了,也以这道题为例谈一谈搜索。
这道题因为皇后可以吃掉八个方向上的棋子,也就意味着皇后的八个方向上是不能再放皇后的,所以我的思路就是每放一个皇后,就把这个皇后八个方向上的所有位置都打上标记,下一次再放皇后时,就找没有打上标记的地方放。
接下来是代码:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int a[20],b[20],c[20],d[20];
int ans;
void print(){
for(int i=1;i<=8;i++){
printf("%d ",a[i]);
}
printf("\n");
ans++;//累加器,用于记录解的个数;
}
void cwy(int i){
if(i>8){
//边界条件的判断;
print();
return;
}
for(int j=1;j<=8;j++){
if(b[j]==0&&c[i+j]==0&&d[i-j+8]==0){
//条件判断;
a[i]=j;b[j]=1;c[i+j]=1;d[i+8-j]=1;//给不能放皇后的地方打上标记;
cwy(i+1);
b[j]=0;c[i+j]=0;d[i-j+8]=0;//回溯,去掉标记;
}
}
}
int main()
{
cwy(1);
printf("**%d**",ans);
return 0;
}
至于 n 皇后,实际上和八皇后一模一样,只是把 8 都改成了 n 。。。
不过为了练搜索,我还是又写了一遍。
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int n,ans;
int a[100001],b[100001],c[100001],d[100001];
void print()
{
if(ans<=2){
for(int i=1;i<=n;i++){
printf("%d ",a[i]);
}
printf("\n");
}
ans++;
}
void cwy(int i)
{
if(i>n){
print();
return;
}
for(int j=1;j<=n;j++){
if(b[j]==0&&c[j+i]==0&&d[i-j+n]==0){
b[j]=1;c[j+i]=1;d[i-j+n]=1;
a[i]=j;
cwy(i+1);
b[j]=0;c[j+i]=0;d[i-j+n]=0;
}
}
}
int main()
{
scanf("%d",&n);
cwy(1);
printf("%d",ans);
return 0;
}
2.自然数的拆分问题
题目描述
任何一个大于1的自然数n,总可以拆分成若干个小于n的自然数之和。现在给你一个自然数n,要求你求出n的拆分成一些数字的和。每个拆分后的序列中的数字从小到大排序。然后你需要输出这些序列,其中字典序小的序列需要优先输出。输入格式
输入:待拆分的自然数n。输出格式
输出:若干数的加法式子。输入输出样例
输入
7
输出
1+1+1+1+1+1+1
1+1+1+1+1+2
1+1+1+1+3
1+1+1+2+2
1+1+1+4
1+1+2+3
1+1+5
1+2+2+2
1+2+4
1+3+3
1+6
2+2+3
2+5
3+4
这道自然数的拆分问题,总结起来有两个要点,一是每个拆分的序列中,数字需要按从小到大的顺序输出;二是序列要按照字典序的顺序输出。第二点比较好解决,搜索中的循环从 1 开始即可,第一点可以通过每次循环存一下这层循环所取的值,并在下一层循环中从这一个值开始循环(由于本题本身就需要存每一层循环所取的值,所以顺带就把这项工作做了),这样就可以保证下一次循环中所取的值不小于这一层循环所取的值,进而使数字按从小到大的顺序输出。另外,如果把第二点做好,就不用考虑输出重复的问题了。
代码:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int n,a[10001]={
1},sum,y,vis[10001];
void print(int);
void cwy(int x,int y){
//x代表还有多少数可以拆;
//y代表层数;
if(x==0){
print(y);
}
for(int i=a[y-1]/*从上一次存入的值开始这一次循环,保证数字按照从小到大的顺序输出;;*/;i<=x&&i<n;i++){
a[y]=i;//存当前循环所取的值;
x-=i;
cwy(x,y+1);
x+=i;//回溯;
}
}
void print(int y){
printf("%d=",n);
for(int i=1;i<=y-2;i++){
printf("%d+",a[i]);
}
printf("%d\n",a[y-1]);
}
int main()
{
scanf("%d",&n);
cwy(n,1);
return 0;
}
今天就写到这,有关汉诺塔的问题明天再写,我得去狂肝作业了。