搜索与回溯 学习笔记

板子题

原题网址P1219 八皇后
八皇后

题目描述

一个如下的 6 × 6 6\times6 6×6 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。
Alt
上面的布局可以用序列 246135 2 4 6 1 3 5 246135 来描述,第 i i i个数字表示在第 i i i行的相应位置有一个棋子,如下:
行号 123456 1 2 3 4 5 6 123456
列号 246135 2 4 6 1 3 5 246135
这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。
并把它们以上面的序列方法输出,解按字典顺序排列。
请输出前 3 3 3个解。最后一行是解的总个数。

输入格式

一行一个正整数 n n n,表示棋盘是 n × n n \times n n×n大小的。

输出格式

前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。

输入输出样例

输入 # 1

6

输出 #1

2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4
说明/提示

【数据范围】
对于 100 % 100\% 100%的数据, 6 ≤ n ≤ 13 6≤n≤13 6n13

题目翻译来自NOCOW。
USACO Training Section 1.5

算法理解

暴搜嘛,其实可以做 99 % 99\% 99%的题目,但是其中 99.999 % 99.999\% 99.999%的题目会TLE,得分一般看造数据的良心,一般只有 20 20 20~ 80 80 80分,如果想A,对于一些数据比较弱的题目,只要剪枝优化或者使用记忆化搜索就可以了,但是对于像01背包这样的题目,还是改进算法更靠谱。
但是,每道题目,当想不出方法时,不能让 100 100 100分就白白流去
方法一:骗分
方法二:暴搜(敲重点)

算法实现

使用一个递归函数即可。

代码实现

这里给出模板伪代码)。

void search(int k){
	if(到达目的地){ 输出解;return;  }
	for(int i=1;i<=算符总数;i++){
		if(不满足条件) continue;
		保存结果;
		进行递归前的操作;
		search(k+1);
		恢复:递归前一步的内容;
		{回溯一步} 
	} 
}

注意:这是我自己习惯的一个模板,有自己的码风,与书上的略有不同,勿喷。

算法复杂度

设总共有 N N N种独立的选择(类似于八皇后的几行,01背包的物品数量),每一种选择的选择方案(类似八皇后的列数,多重背包的每件物品的个数)为 a i a_i ai ( 1 ≤ i ≤ N ) (1\leq i\leq N) (1iN)那么,复杂度就为 O ( a 1 × a 2 × a 3 . . . a N − 1 × a N ) O(a_1 \times a_2 \times a_3 ...a_{N-1}\times a_N) O(a1×a2×a3...aN1×aN)如果想八皇后、01背包一样 a 1 = a 2 = a 3 . . . a n − 1 = a n a_1 = a_2 = a_3 ...a_{n-1} = a_n a1=a2=a3...an1=an,设 M = a 1 M=a_1 M=a1那么复杂度为 O ( M N ) O(M^N) O(MN),非常慢,但是对于小数据还是非常爽的。

板子题-解析

先看一下数据范围:
6 ≤ n ≤ 13 6≤n≤13 6n13
在简单的算一下:
1 3 2 = 169 13^2=169 132=169
1 3 3 = 2197 13^3=2197 133=2197
1 3 4 = 28561 13^4=28561 134=28561
1 3 5 = 371293 13^5=371293 135=371293
1 3 6 = 4826809 13^6=4826809 136=4826809
1 3 7 = 62748517 13^7=62748517 137=62748517
1 3 8 = 815730721 13^8=815730721 138=815730721
时间还是比较充裕的。

解法1

首先,开一个 s e e [ 39 ] [ 39 ] see[39][39] see[39][39]的数组来贮存这个位置是否被占取,利用全局变量的初值,0为未占取,1为已占取。
代码如下:

#include<cstdio>
#define maxn 39
using namespace std;
int n,see[maxn][maxn],ans[maxn],sum,a[maxn];
void search(int k){
	if(k==n+1){
		sum++;
		if(sum<=3){
			for(int i=1;i<=n;i++)
		       printf("%d ",a[i]);
		    printf("\n");
		}
		return;
	}
	register int i,j;
	for(int i=1;i<=n;i++){
		if(k==1){
			for(int x=1;x<=n;x++)
		        for(int y=1;y<=n;y++) see[x][y]=0;
		}
		if(see[k][i]==1) continue;
		a[k]=i;
		register int x=k,y=i;
		int now[maxn][maxn]={{0}};
		for(x=1;x<=n;x++)
		    for(y=1;y<=n;y++)
		        now[x][y]=see[x][y];
		
		for(int j=1;j<=n;j++) see[j][i]=1;
		x=k,y=i;
		while(1<=x&&x<=n&&1<=y&&y<=n){ see[x][y]=1; x++;y++; }
		x=k,y=i;
		while(1<=x&&x<=n&&1<=y&&y<=n){ see[x][y]=1; x++;y--; }
		x=k,y=i;
		while(1<=x&&x<=n&&1<=y&&y<=n){ see[x][y]=1; x--;y++; }
		x=k,y=i;
		while(1<=x&&x<=n&&1<=y&&y<=n){ see[x][y]=1;x--;y--; }
		search(k+1);
		
		for(x=1;x<=n;x++)
		    for(y=1;y<=n;y++)
		        see[x][y]=now[x][y];
	}
}
int main(){
    scanf("%d",&n);
    search(1);
    printf("%d",sum);
    return 0;
}

测评结果:
ACACACACACACACTLE
he~~~~
TLE了一个
在这里插入图片描述
啊~~~~~~~~~~~~

可以看见,第 7 7 7个测试点已经是 0.8 0.8 0.8秒的了,看来,还得优化一波~~~~
当然是寄存器和右上角的O2啦
好了,不扯了,加下来请看——————

解法2–AC

二话不说,先上图:

Y\X12345678
1|/
2|/
3\|/
4\|/
5-------
6/|\
7/|\
8|\

▲表示皇后的位置
| - \ /表示皇后控制的区域,也就是说,如果皇后放在▲上,这些位置就不能放皇后了。
也就是说,我们只要开3个数组,因为我们是一行一行搜索的,就没必要统计“-”的位置了。

在仔细看,你会发现:
1.“|”永远位于同一列;
2.“\”行与列的差永远一样,由于C++不能处理负下标的数组(会RE),所以在用数组表示的时候,要加上一个数(这里用 n n n);
3.“/”行与列的和永远一样。

l i n e line line 数组表示“|”,用l表示;
l l l 数组表示“/”
r r r 数组表示“\”(别忘了加上 n n n
代码君献上AC代码:
注:这题我用了 n n n小时, n > 2 n>2 n>2

#include<cstdio>
#define maxn 39
using namespace std;
int n,line[maxn],l[maxn],r[maxn],sum,a[maxn];
void search(int k){
	if(k==n+1){
		sum++;
		if(sum<=3){
			for(int i=1;i<=n;i++)
		       printf("%d ",a[i]);
		    printf("\n");
		}
		return;
	}
	int i,j;
	for(int i=1;i<=n;i++){
		if(line[i]==1||l[k+i]==1||r[k-i+n]==1) continue;
		a[k]=i;
		line[i]=1; l[k+i]=1; r[k-i+n]=1;
		search(k+1);
		line[i]=0; l[k+i]=0; r[k-i+n]=0;
	}
}
int main(){
    scanf("%d",&n);
    search(1);
    printf("%d",sum);
    return 0;
}

结尾福利:
祝同学们新年快乐!
祝武汉人民度过难关!
最后记得打 1 1 1 28 28 28日(农历初四) 14 : 00 14:00 14:00~ 18 : 00 18:00 18:00的月赛哦~~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值