0x29「搜索」练习
2901 靶形数独
直接爆搜就是。
有两个优化:
1 每次选择可填数字最少的格子填写。
2 位运算常数优化
关于位运算,具体地说,我们将行号,列号,九宫格号从0~8编号,然后维护三个数组,保存若干个二进制数。
row[i]:表示第i行可以填写的情况,第j位若为1,就表示这一行可以填写数字j+1。
col[i]:表示第i列可以填写的情况,第j位若为1,就表示这一列可以填写数字j+1。
grid[i]:表示第i个九宫格可以填写的情况,第j位若为1,就表示这一个九宫格可以填写数字j+1。
这三个数组初始化如下
for(int i=0;i<=8;i++) row[i]=col[i]=grid[i]=(1<<9)-1;
除此之外,我们还需要维护两个数组。
cnt[i]:表示二进制数i中1的个数。
num[i]:i是2的整数次幂,表示。
这两个数组初始化如下
for(int i=0;i<=8;i++) row[i]=col[i]=grid[i]=(1<<9)-1;
for(int i=0;i<(1<<9);i++) for(int j=i;j;j-=j&-j) cnt[i]++;
内联函数g(x,y)可以求出x行y列的格子对应的九宫格编号。
inline int g(int x,int y){
return (x/3)*3+y/3;
}
flip(x,y,z)函数可以将x行y列的格子能否填写数字z+1的状态取反。
void flip(int x,int y,int z){
row[x]^=1<
通过如下语句可以得到(i,j)这个格子能填的数字对应的二进制数。
int val=row[i]&col[j]&grid[g(i,j)];
通过如下语句可以将val里头的每一位取出来,并将其变成对应的数字z。
for(int i=val;i;i-=i&-i){
int z=num[i&-i];
//other code
}
完整代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
2902 虫食算
从低位向高位依次考虑第x个字母可能放的数字。因为是n进制加法,所以每一位的数字范围为[0,n-1]。
这题实现较为繁琐,具体细节详见代码注释。
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
2903 Mayan游戏
两个剪枝:
1 当一种颜色的方块只有1个或2个时,肯定无法消除。
2 两个方块交换时,左边的方块右移和右边的方块左移等效,所以只需考虑其中一种。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
POJ1167 The Buses
题意:给出n(n<=300)个数字,数字范围在[0,59]之间,要求找出最少的等差数列,不重不漏的覆盖所有数字。
method_1是自己写的IDdfs,由于每层递归时采用了枚举首项和公差的方式,所以有一个重要的可行性剪枝无法加入,最终TLE。
method_2是dfs+剪枝,预处理出m条可能的路线 ,按照覆盖的点数降序排序,因此可以做到dfs中的最优性剪枝。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
IDdfs
TLE
*/
#include
#include
#include
#include
#include
#include
PS:这道题上我开始IDdfs中忘记写还原现场的操作,所以写了个拍子,这里顺便附上拍子的数据生成器代码和对拍代码。
数据生成器代码
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
对拍代码
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
2906 武士风度的牛
坐标变换下的最短路,用bfs即可(因为bfs第一次到达终点的时候,就得到了最优解,所以比dfs效率更优)。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
2907 乳草的入侵
类似于上一题,只不过这一次在八向联通的情况下,求最长路,仍然使用bfs即可,不过本题需要特别注意题目中行列的含义。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
2908 字串变换
考虑到变换规则可逆,起始状态和最终状态都确定,每一层搜索树分支较多,搜索树的最深层数不超过10,这四个特征,我们可以使用双向BFS求解。
代码如下
/*
双向BFS:
1.正向搜索:从初始结点向目标结点方向搜索,按照正向规则(A$->B$)变换。
2.逆向搜索:从目标结点向初始结点方向搜索,按照逆向规则(B$->A$)变换。
当两个方向的搜索生成同一子结点时终止此搜索过程(变换的总步数为此时两个方向BFS的步数总和)。
双向搜索通常有两种方法:
1. 两个方向交替扩展。
2. 选择结点个数较少的那个方向先扩展。
方法2克服了两方向结点的生成速度不平衡的状态,明显提高了效率。本程序使用方法1,两个方向交替BFS,进行正反规则变换。
*/
#include
#include
#include
#include
using namespace std;
struct Node {
char str[41];
int sep;
Node() {
sep=0;
// memset(str,sizeof(str),0);
}
} q1[20000], q2[20000];
int h1, r1, h2, r2; //h1,r1...h2,r2,分别保存起始和目标两个状态的队列头和尾
char s1[6][25], s2[6][25]; //存储变换规则
int n,Min=100; //Min储存最少规则变换次数
void copy2(int start, int use) { //逆向搜索,当q2[]搜索到逆向规则匹配的字串B$的时候,进行替换
int i, j;
r2++; //队列中元素增加1个字串
q2[r2].sep = q2[h2].sep + 1;
for(i = 0; i < start; i++) { //替换匹配规则的字串
q2[r2].str[i] = q2[h2].str[i];
}
for(j = 0; s1[use][j] != '\0'; j++, i++) {
q2[r2].str[i] = s1[use][j];
}
for(j = start + strlen(s2[use]); q2[h2].str[j] != '\0'; j++, i++) {
q2[r2].str[i] = q2[h2].str[j];
}
//cout<<"q2:"< 10) { //正反搜索的步数总和超过了10,说明这样的转换至少要超过10次才能实现,结束
printf("NO ANSWER!\n");
exit(0);
}
for(i = 0; i < strlen(q1[h1].str); i++) {
for(j = 0; j < n; j++) { //正向搜索,一共n个变换规则
if(strncmp(s1[j], &q1[h1].str[i], strlen(s1[j])) == 0) { // 从q1[h1].str[i]开始的位置比较 比较长度为strlen(s1[j])
copy1(i, j);
}
}
}
h1++; //正向一遍BFS,搜索完所有规则之后,队首元素出队
for(i = 0; i < strlen(q2[h2].str); i++) { //加快搜索的速度,同理从目标开始,方向,并根据逆向规则进行BFS
for(j = 0; j < n; j++) {
if(strncmp(s2[j], &q2[h2].str[i], strlen(s2[j])) == 0) {
copy2(i, j);
}
}
}
h2++;
}
}
int main() {
//freopen("字串变换.in","r",stdin);
scanf("%s%s", q1[0].str, q2[0].str);
while(scanf("%s%s", s1[n], s2[n]) == 2) {
n++;
}
work();
printf("NO ANSWER!\n");
return 0;
}
POJ2044 Weather Forecast
由于四个角(1,1)(1,4)(4,1)(4,4)最难以照顾 所以判断是否有城市连续7天不下雨只需判断4个角即可。
有了这个大优化,我们就能够在空间允许的范围内表示状态了。
struct node{
int x,y,d,s1,s2,s3,s4;//云层左上角在(x,y) 当前为第d天 (1,1)(1,4)(4,1)(4,4)没有下雨的天数分别为s1,s2,s3,s4
};
另外,为了记忆化状态,我们采用vis[a,b,c,d,e,f,g,h]表示第c天 云层左上角在(a,b)时 (1,1)(1,4)(4,1)(4,4)没有下雨的天数分别为e,f,g,h的状态是否到达过。
PS:这道题目bfs写法不能全部AC,即代码中method_1是TLE版本,method_2用dfs可以AC。
代码如下
/*
method_1用bfs TLE
method_2用dfs 340msAC
*/
#define method_1
#ifdef method_1
/*
由于四个角(1,1)(1,4)(4,1)(4,4)最难以照顾 所以判断是否有城市连续7天不下雨只需判断4个角即可
*/
#include
#include
#include
#include
#include
#include
POJ1945 Power Hungry Cows
首先考虑表示状态,每个状态包括一个数组,即达到当前值中途所产生的所有值。但是这样数组过大,所以会选择只用一个二元组(a,b)表示目前这一次操作用到了两个值。为了避免(a,b)和(b,a)表示重复,所以我们令。
但是这样状态依然很多,所以我们采用启发式bfs,即算法。估价函数为把a不断自加直至>=n所需的次数(注意a>=b),显然实际所需次数不会小于这一次数。
因此最终的状态为一个四元组(a,b,val,cnt)。未来估价val,当前次数cnt。并将四元组存入优先队列,按照cnt+val升序排序。
同样我们采用链式前向星(手写hash)对状态判重。hash函数为,并取模。
代码如下
/*
*/
#define method_1
#ifdef method_1
/*
*/
#include
#include
#include
#include
#include
#include
2912 Flood-it!
看到一个较为复杂状态的dfs求最优解,想到迭代加深搜索。
看到局面可以估价,想到启发式搜索。
因此可以使用IDA-dfs。
考虑估价函数f()的设计。
int f() { //估价函数 求a数组中除了(1,1)所在联通块的颜色,总共有sum种不同颜色
int sum=0;
memset(cnt,0,sizeof(cnt));
for(int i=1; i<=n; i++) for(int j=1; j<=n; j++) if(vis[i][j]!=1&&!cnt[a[i][j]]) {
cnt[a[i][j]]=1,sum++;
}
return sum;
}
但是这样直接加上dfs/bfs灌水,依然无法ac,因此我们需要考虑增加其他优化。
优化1 每次朝着(1,1)所在联通块变大的方向搜索。
优化2 每次已经灌水过的部分不再理会,只朝着没有灌水过的部分推进 。
代码如下(其中method_1为不加如上两个优化的版本,50分,剩余部分TLE,method_2为100分)
/*
*/
#define method_2
#ifdef method_1
/*
先用了dfs来灌水,后来超时50分,又改成了用bfs灌水,还是TLE
*/
#include
#include
#include
#include
#include
#include