[NOIP 2014复习]第二章:搜索
一、深度优先搜索(DFS)
1、Wikioi 1066引水入城
题目描写叙述 Description
在一个遥远的国度,一側是风景秀美的湖泊,还有一側则是漫无边际的沙漠。该国的行政 区划十分特殊,刚好构成一个N行M列的矩形,如上图所看到的,当中每一个格子都代表一座城 市,每座城市都有一个海拔高度。 为了使居民们都尽可能饮用到清澈的湖水,如今要在某些城市建造水利设施。水利设施 有两种,分别为蓄水厂和输水站。蓄水厂的功能是利用水泵将湖泊中的水抽取到所在城市的 蓄水池中。因此,仅仅有与湖泊毗邻的第1行的城市能够建造蓄水厂。而输水站的功能则是通 过输水管线利用高度落差,将湖水从高处向低处输送。故一座城市能建造输水站的前提,是
存在比它海拔更高且拥有公共边的相邻城市,已经建有水利设施。因为第N行的城市靠近沙漠。是该国的干旱区。所以要求当中的每座城市都建有水利 设施。
那么,这个要求是否能满足呢?假设能,请计算最少建造几个蓄水厂;假设不能。求干 旱区中不可能建有水利设施的城市数目。
输入描写叙述 Input Description
输入的每行中两个数之间用一个空格隔开。
输入的第一行是两个正整数N和M,表示矩形的规模。 接下来N行,每行M个正整数,依次代表每座城市的海拔高度。
输出描写叙述 Output Description
输出有两行。假设能满足要求。输出的第一行是整数1,第二行是一个整数,代表最少 建造几个蓄水厂;假设不能满足要求。输出的第一行是整数0,第二行是一个整数。代表有 几座干旱区中的城市不可能建有水利设施。
例子输入 Sample Input
2 5
9 1 5 4 3
8 7 6 1 2
例子输出 Sample Output
1
1
数据范围及提示 Data Size & Hint
【数据范围】 本题共同拥有10个測试数据,每一个数据的范围例如以下表所看到的: 測试数据编号 是否能满足要求 N M 1 不能 ≤ 10 ≤ 10 2 不能 ≤ 100 ≤ 100 3 不能 ≤ 500 ≤ 500 4 能 = 1 ≤ 10 5 能 ≤ 10 ≤ 10 6 能 ≤ 100 ≤ 20 7 能 ≤ 100 ≤ 50 8 能 ≤ 100 ≤ 100 9 能 ≤ 200 ≤ 200 10 能 ≤ 500 ≤ 500 对于全部的10个数据,每座城市的海拔高度都不超过10^6
例子2 说明
数据范围
这个题简单点说,就是给一个每一个坐标有高度的棋盘,然后要在棋盘的一边。在一些点上灌水,依据水往低处流的原理,让水能流到对面一边。要求推断能否让还有一边所有有水,假设能,求出最少要给多少个点浇水。才干使还有一边所有有水。
对于第一问来说,能够从出发的一边開始,将这一行全部的格子都浇上水,然后推断对面那一边是否全部的格子都有水。第二问处理复杂非常多,须要从出发的一边。每次仅仅对一个格子浇水,然后找到对面那一边的有水的格子的区间(能够证明这个区间是连续的,我之前的一篇文章里有证明),将每一个格子相应的区间当成线段来看待,第二问就转化成了给定m条线段,要覆盖[1,m]区间至少要多少条的问题,能够用贪心来做。
如图。先将全部线段依据左端点升序排序。然后每次寻找一条线段,保证这条线段与之前的覆盖区间重合或相连,且线段的右端点大于之前覆盖区间的右端点(就是说添加这条线段能添加覆盖区间),在满足这些条件的线段中找右端点最大的一条。并插入覆盖区间,更新覆盖区间。能够证明,这个贪心思想是正确的。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <queue> #define mem(array,num) memset(array,num,sizeof(array))
#define MAXN 600
#define lowbit(num) ((x&(-x)) using namespace std; struct Line
{
int num,L,R; //左端点、右端点
}line[MAXN]; int map[MAXN][MAXN],xx[]={1,-1,0,0},yy[]={0,0,1,-1};
int visit[MAXN][MAXN];
int high[MAXN][MAXN];
int n,m; bool cmp(Line a,Line b)
{
if(a.L!=b.L) return a.L<b.L;
else return a.R<b.R;
} void flood(int x,int y) //对棋盘灌水染色
{
if(visit[x][y]) return; //之前訪问过了,不必反复搜索
visit[x][y]=1;
map[x][y]=1;
for(int dir=0;dir<4;dir++) //四个方向寻找下一个染色的点,这个点高度必须比(x,y)小
{
int newx=x+xx[dir],newy=y+yy[dir];
if(newx>=1&&newx<=n&&newy>=1&&newy<=m)
if(high[newx][newy]<high[x][y]) //新的点高度比(x,y)小
flood(newx,newy);
}
} void greed() //贪心求最少建立几个蓄水池,换句话说就是求最少线段覆盖
{
int pointer=0,ans=0,maxRight=0,nextLine=0;
sort(line+1,line+m+1,cmp); //依照线段右端点升序排序
while(maxRight<m) //整个区间还没有被全然覆盖
{
int maxR=0; //新增加的线段的最大右端点
for(int i=pointer+1;i<=m;i++) //寻找一个新的线段
{
if(line[i].L<=maxRight+1) //该线段的和覆盖区间有重合
{
if(line[i].R>maxRight&&line[i].R>maxR)
{
maxR=line[i].R;
nextLine=i; //下一条增加的线段标记为i
}
}
}
ans++;
pointer=nextLine;
maxRight=maxR;
}
printf("1\n%d\n",ans);
}
int main()
{
int noWaterCityNum=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&high[i][j]);
for(int i=1;i<=m;i++)
flood(1,i);
for(int i=1;i<=m;i++)
if(!map[n][i])
noWaterCityNum++;
if(noWaterCityNum)
{
printf("0\n%d\n",noWaterCityNum);
system("pause");
return 0;
}
for(int i=1;i<=m;i++) //以棋盘上的点(1,i)作为起点開始染色
{
bool flag=false;
mem(map,0); //棋盘清空
mem(visit,0);
flood(1,i); //灌水法染色
for(int j=1;j<=m+1;j++) //从1到m寻找最后一行(n,j),找到(1,i)染色后在最后一行留下的区间
{
if(map[n][j]&&!flag)
{
if(!line[i].L) //找到了区间起点
{
line[i].L=j;
flag=true;
}
}
else if(!map[n][j]&&flag)
{
line[i].R=j-1; //找到了区间终点
break;
}
}
}
greed(); //贪心求最少要几个输水站
system("pause");
return 0;
}
2、Wikioi 1116 四色问题
题目描写叙述 Description
给定N(小于等于8)个点的地图,以及地图上各点的相邻关系。请输出用4种颜色将地图涂色的全部方案数(要求相邻两点不能涂成同样的颜色)
数据中0代表不相邻,1代表相邻
输入描写叙述 Input Description
第一行一个整数n,代表地图上有n个点
接下来n行,每行n个整数。每一个整数是0或者1。第i行第j列的值代表了第i个点和第j个点之间是相邻的还是不相邻,相邻就是1,不相邻就是0.
我们保证a[i][j] = a[j][i] (a[i,j] = a[j,i])
输出描写叙述 Output Description
染色的方案数
例子输入 Sample Input
8
0 0 0 1 0 0 1 0
0 0 0 0 0 1 0 1
0 0 0 0 0 0 1 0
1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0
1 0 1 0 0 0 0 0
0 1 0 0 0 0 0 0
例子输出 Sample Output
15552
数据范围及提示 Data Size & Hint
n<=8
题目太简单了就不多说了,数据太弱,非常easy的DFS,用数组保存当前各个点的颜色即可了,没什么要注意的地方
#include <stdio.h>
#include <string.h>
#include <stdlib.h> #define MAXN 10 int color[MAXN],relative[MAXN][MAXN]; //color[i]=i点的颜色,relative[i][j]=1表示i和j相邻
int n,totalSolution=0; //totalSolution=总计方案数 void dfs(int x) //对点x染色
{
if(x>n) //全部点染色完了
{
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i!=j)
if(relative[i][j]) //寻找两个相邻的点i,j
if(color[i]==color[j])
return; //有相邻的点颜色同样。直接返回
totalSolution++; //符合条件。添加方案数,返回
return;
}
for(int i=1;i<=4;i++) //对点x染颜色i
{
color[x]=i;
dfs(x+1);
}
} int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&relative[i][j]);
dfs(1);
printf("%d\n",totalSolution);
return 0;
}
3、Wikioi 1064 虫食算
题目描写叙述 Description
所谓虫食算,就是原先的算式中有一部分被虫子啃掉了,须要我们依据剩下的数字来判定被啃掉的字母。
来看一个简单的样例:
43#9865#045
+ 8468#6633
44445506978
当中#号代表被虫子啃掉的数字。依据算式。我们非常easy推断:第一行的两个数字各自是5和3。第二行的数字是5。如今。我们对问题做两个限制:
首先。我们仅仅考虑加法的虫食算。这里的加法是N进制加法,算式中三个数都有N位。同意有前导的0。
其次,虫子把全部的数都啃光了,我们仅仅知道哪些数字是同样的。我们将同样的数字用同样的字母表示。不同的数字用不同的字母表示。假设这个算式是N进制的,我们就取英文字母表午的前N个大写字母来表示这个算式中的0到N-1这N个不同的数字:可是这N个字母并不一定顺序地代表0到N-1)。输入数据保证N个字母分别至少出现一次。
BADC
+ CBDA
DCCC
上面的算式是一个4进制的算式。非常显然,我们仅仅要让ABCD分别代表0123。便能够让这个式子成立了。你的任务是,对于给定的N进制加法算式,求出N个不同的字母分别代表的数字。使得该加法算式成立。输入数据保证有且仅有一组解,
输入描写叙述 Input Description
输入包括4行。
第一行有一个正整数N(N<=26),后面的3行每行有一个由大写字母组成的字符串,分别代表两个加数以及和。这3个字符串左右两端都没有空格。从高位到低位,而且恰好有N位。
输出描写叙述 Output Description
输出包括一行。
在这一行中,应当包括唯一的那组解。解是这样表示的:输出N个数字,分别表示A,B,C……所代表的数字。相邻的两个数字用一个空格隔开。不能有多余的空格。
例子输入 Sample Input
5
ABCED
BDACE
EBBAA
例子输出 Sample Output
1 0 3 4 2
数据范围及提示 Data Size & Hint
对于30%的数据,保证有N<=10;
对于50%的数据,保证有N<=15。
对于所有的数据,保证有N<=26。
看得出来,虫食算是个线性方程组。并且官方标程也是用高斯消元来解的,只是比赛时非常多同学不会高斯消元,都是用DFS爆搜+强剪枝骗分的,DFS的思路非常清晰。首先要离散化,假设不想用map来存答案的话,就须要把输入数据中的字母统计成一个字母表,给每一个字母一个标号当成下标。然后依据这个字母表中字母的顺序。依次枚举每一个字母相应的数字。全部字母枚举完后。检查答案是否正确。假设正确。输出答案,结束程序。
只是这样做肯定没多少分。由于N<=26。范围非常大,所以须要剪枝。详细剪枝思路例如以下:
1、假设当前位i。a[i]、b[i]、c[i]已知,就检查a[i]+b[i]算上进位和不算进位是否等于c[i]。都不等于,则不再继续搜索
2、假设当前位i,a[i]、b[i]、c[i]中有两个已知,就依据算上进位和不算上进位的情况考虑还有一个位置的数字的答案,若这个答案相应的数字之前已经被使用过了。则不再继续搜索。
这两条剪枝很重要,有好几个推断,少一个推断都不行。
另外程序还有几个坑点,我来总结下:
1、字母表应该由低位的字母到高位的字母依次插入。由于搜索时假设低位字母已知的多一些。剪枝效果会好非常多,这种顺序能够保证低位的数字先猜出来,高位的后猜出来。假设插入顺序反了。会导致搜索效率严重下降。剪枝的优势体现不出来。
2、假设找到了答案。立马停止搜索,由于搜索树中每个结点的儿子非常多。假设不停止搜索,还会继续搜索那些没实用的子树,这些部分非常大,会严重影响搜索速度。
总结:假设会高斯消元的话。我不建议考试时用爆搜做这个题,由于爆搜加剪枝不仅写起来麻烦,并且非常easy由于剪枝写错了写漏了而TLE扣分,甚至会接近爆零,高斯消元也不难。代码简单。最好还是考虑学学高斯消元,以备不时之需,小心考试时的高斯消元题不会做,用爆搜去做非常难拿高分。
以下是代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <string>
#include <iostream> #define MAXN 100 using namespace std; string A,B,C,word; //A+B=C,最高位为n-1,最低位为0!
int n,num[MAXN],ans[MAXN]; //N进制数,ans[]数组保存答案
int hash[270],used[27]; //hash[i]=1表示字母i已经增加了单词表,permutation[i]=字母i在word表里的位置,used[i]=1表示已经尝试过了字母i的相应数字
bool hasFinished=false; bool isWrongAns() //检查已经算出来的答案是否正确。假设答案没算完,可是算出来的部分正确。就当成正确
{
int c,g=0;
for(int i=n-1;i>=0;i--) //从低位到高位
{
if(A[i]>=n||B[i]>=n||C[i]>=n) //有字母没有被替换
return false;
c=A[i]+B[i]+g;
if(c%n!=C[i]) //a+b!=c 则说明猜的数字有问题
return true;
g=c/n; //计算进位
}
return false;
} bool check() //检查当前状态下已经算出的部分是否有条件冲突
{
int c,g=0,x1,x2;
//1、已知a[i] b[i] c[i]
for(int i=n-1;i>=0;i--)
{
if(A[i]>=n||B[i]>=n||C[i]>=n)
continue;
c=(A[i]+B[i])%n; //c=a[i]+b[i]
if(!(c==C[i]||(c+1)%n==C[i])) return true; //不管进不进位,答案都不正确
}
//2、已知a[i] b[i]
for(int i=n-1;i>=0;i--)
{
if(!(A[i]<n&&B[i]<n&&C[i]>=n))
continue;
x1=(A[i]+B[i])%n; //c=a[i]+b[i]
x2=(A[i]+B[i]+1)%n;
if(used[x1]&&used[x2]) return true; //不管进不进位,答案都不正确
}
//3、已知a[i] c[i]
for(int i=n-1;i>=0;i--)
{
if(!(A[i]<n&&B[i]>=n&&C[i]<n))
continue;
x1=(C[i]+n-A[i])%n;
x2=(x1-1)%n;
if(used[x1]&&used[x2]) return true; //不管进不进位。答案都不正确
}
//4、已知b[i] c[i]
for(int i=n-1;i>=0;i--)
{
if(!(A[i]>=n&&B[i]<n&&C[i]<n))
continue;
x1=(C[i]+n-B[i])%n;
x2=(x1-1)%n;
if(used[x1]&&used[x2]) return true; //不管进不进位,答案都不正确
}
return false;
} void outAns()
{
for(int i=0;i<n;i++)
ans[word[i]-65]=num[i];
for(int i=0;i<n;i++)
printf("%d ",ans[i]);
printf("\n");
exit(0);
} string change(string s,char a,char b) //把字符串s中的字母a都换成b
{
for(int i=0;i<n;i++)
if(s[i]==a) s[i]=b;
return s;
} void dfs(int p) //正在尝试word里的第p个字母
{
string copyA,copyB,copyC;
if(hasFinished)
return;
if(isWrongAns())
return;
if(check())
return;
if(p==n) //全部的字母都试过了,答案成立。就输出答案
{
outAns();
return;
}
for(int i=n-1;i>=0;i--) //用数字i去尝试取代第p个字母
{
if(!used[i]) //数字i没实用过
{
used[i]=true;
copyA=A,copyB=B,copyC=C; //将A、B、C拷贝保存
A=change(copyA,word[p],i);
B=change(copyB,word[p],i);
C=change(copyC,word[p],i);
num[p]=i; //第p个字母相应的数字是i
dfs(p+1); //尝试下一个字母相应的数字
A=copyA,B=copyB,C=copyC;
used[i]=false;
}
}
} int main()
{
scanf("%d",&n);
cin>>A>>B>>C;
for(int i=n-1;i>=0;i--)
{
if(!hash[A[i]])
{
word+=A[i]; //假设之前没有增加过这个字母。将这个字母增加单词表
hash[A[i]]=1;
}
if(!hash[B[i]])
{
word+=B[i]; //假设之前没有增加过这个字母,将这个字母增加单词表
hash[B[i]]=1;
}
if(!hash[C[i]])
{
word+=C[i]; //假设之前没有增加过这个字母,将这个字母增加单词表
hash[C[i]]=1;
}
}
dfs(0);
return 0;
}
4、POJ 1724 ROADS
Description
Bob and Alice used to live in the city 1. After noticing that Alice was cheating in the card game they liked to play, Bob broke up with her and decided to move away - to the city N. He wants to get there as quickly as possible, but he is short on cash.
We want to help Bob to find the shortest path from the city 1 to the city Nthat he can afford with the amount of money he has.
Input
The second line contains the integer N, 2 <= N <= 100, the total number of cities.
The third line contains the integer R, 1 <= R <= 10000, the total number of roads.
Each of the following R lines describes one road by specifying integers S, D, L and T separated by single blank characters :
- S is the source city, 1 <= S <= N
- D is the destination city, 1 <= D <= N
- L is the road length, 1 <= L <= 100
- T is the toll (expressed in the number of coins), 0 <= T <=100
Notice that different roads may have the same source and destination cities.
Output
If such path does not exist, only number -1 should be written to the output.
Sample Input
5
6
7
1 2 2 3
2 4 3 3
3 4 2 4
1 3 4 1
4 6 2 1
3 5 2 0
5 4 3 2
Sample Output
11
Source
这个题事实上是个图论的题。不适合用DFS做。只是郭老师的搜索课件里有提过这个题。我就用DFS来做试试。
用三元组(n,cost,dist)来表示一个搜索状态。搜索到了点n,总的花费是cost,距离是dist
DFS本来跑得就慢,你们懂的,所以须要非常多剪枝优化:
1、假设之前已经求过到达点n、花费为cost的最小距离。且之前的解更优,就不继续搜索
2、假设花费cost>K,就是说没这么多钱去交过路费,也不继续搜索
3、每次dfs时若点n被訪问过,不继续搜索,否则标记点n被訪问过
缺一条剪枝都不行。这个题时间要求太苛刻了
#include <stdio.h>
#include <string.h>
#include <stdlib.h> #define MAXE 110
#define MAXV 10100
#define MAXM 10100
#define INF 0x3f3f3f3f int f[MAXE][MAXM]; //f[i][cost]=到达i点,花了过路费cost时的最少距离 struct edge
{
int u,v,w,cost; //起点,终点,距离,过路费
int next;
}edges[MAXV]; int head[MAXE],nCount=0,K,N,R,totalCost=0,totalDist=0,minDist=INF;
bool visit[MAXE]; void AddEdge(int U,int V,int W,int COST)
{
edges[++nCount].u=U;
edges[nCount].v=V;
edges[nCount].w=W;
edges[nCount].cost=COST;
edges[nCount].next=head[U];
head[U]=nCount;
} void dfs(int n) //到达了n点。一共花了过路费cost元,最短距离为dist
{
if(n==N)
{
if(totalDist<minDist) minDist=totalDist;
return;
}
for(int p=head[n];p!=-1;p=edges[p].next) //向n的儿子进行搜索
if(!visit[edges[p].v])
{
if(totalCost+edges[p].cost>K) continue;
if(totalDist+edges[p].w>minDist||totalDist+edges[p].w>f[edges[p].v][totalCost+edges[p].cost])
continue;
totalCost+=edges[p].cost;
totalDist+=edges[p].w;
f[edges[p].v][totalCost]=totalDist;
visit[edges[p].v]=true;
dfs(edges[p].v);
visit[edges[p].v]=false;
totalCost-=edges[p].cost;
totalDist-=edges[p].w;
}
} int main()
{
int minAns=INF;
memset(head,-1,sizeof(head));
memset(f,INF,sizeof(f));
scanf("%d%d%d",&K,&N,&R);
for(int i=1;i<=R;i++)
{
int u,v,w,cost;
scanf("%d%d%d%d",&u,&v,&w,&cost);
AddEdge(u,v,w,cost);
}
dfs(1);
if(minDist<INF)
printf("%d\n",minDist);
else printf("-1\n");
return 0;
}
5、Wikioi 1174 靶形数独
题目描写叙述 Description
小城和小华都是热爱数学的好学生,近期,他们不约而同地迷上了数独游戏,好胜的他
们想用数独来一比高低。但普通的数独对他们来说都过于简单了。于是他们向Z 博士请教。
Z 博士拿出了他近期发明的“靶形数独”,作为这两个孩子比试的题目。靶形数独的方格同普通数独一样,在 9 格宽×9 格高的大九宫格中有9 个3 格宽×3 格
高的小九宫格(用粗黑色线隔开的)。在这个大九宫格中,有一些数字是已知的。依据这些数字,利用逻辑推理,在其它的空格上填入1 到9 的数字。每一个数字在每一个小九宫格内不能
反复出现。每一个数字在每行、每列也不能反复出现。但靶形数独有一点和普通数独不同。即
每个方格都有一个分值,并且如同一个靶子一样。离中心越近则分值越高。上图详细的分值分布是:最里面一格(黄色区域)为 10 分,黄色区域外面的一圈(红
色区域)每一个格子为9 分,再外面一圈(蓝色区域)每一个格子为8 分。蓝色区域外面一圈(棕
色区域)每一个格子为7 分,最外面一圈(白色区域)每一个格子为6 分,如上图所看到的。比赛的
要求是:每一个人必须完毕一个给定的数独(每一个给定数独可能有不同的填法),并且要争取
更高的总分数。而这个总分数即每一个方格上的分值和完毕这个数独时填在对应格上的数字
的乘积的总和。如图,在下面的这个已经填完数字的靶形数独游戏中,总分数为2829。
游
戏规定,将以总分数的高低决出胜负。因为求胜心切,小城找到了善于编程的你,让你帮他求出。对于给定的靶形数独,能
够得到的最高分数。
输入描写叙述 Input Description
一共 9 行。
每行9 个整数(每一个数都在0—9 的范围内),表示一个尚未填满的数独方
格,未填的空格用“0”表示。每两个数字之间用一个空格隔开。
输出描写叙述 Output Description
输出能够得到的靶形数独的最高分数。假设这个数独无解,则输出整数-1。
例子输入 Sample Input
【输入输出例子 1】
7 0 0 9 0 0 0 0 1
1 0 0 0 0 5 9 0 0
0 0 0 2 0 0 0 8 0
0 0 5 0 2 0 0 0 3
0 0 0 0 0 0 6 4 8
4 1 3 0 0 0 0 0 0
0 0 7 0 0 2 0 9 0
2 0 1 0 6 0 8 0 4
0 8 0 5 0 4 0 1 2【输入输出例子 2】
0 0 0 7 0 2 4 5 3
9 0 0 0 0 8 0 0 0
7 4 0 0 0 5 0 1 0
1 9 5 0 8 0 0 0 0
0 7 0 0 0 0 0 2 5
0 3 0 5 7 9 1 0 8
0 0 0 6 0 1 0 0 0
0 6 0 9 0 0 0 0 1
0 0 0 0 0 0 0 0 6
例子输出 Sample Output
【输入输出例子 1】
2829
【输入输出例子 1】
2852
数据范围及提示 Data Size & Hint
【数据范围】
40%的数据。数独中非0 数的个数不少于30。80%的数据,数独中非0 数的个数不少于26。
100%的数据。数独中非0 数的个数不少于24。
Wikioi上把这个题标了个启示式搜索的标签,于是有人就说这个题要用A*做,我仅仅能呵呵了,这个题和A*没半毛钱关系,由于题目是填数独,作为NP全然问题,数独仅仅能用搜索做,并且是DFS(BFS仅仅能求最少步数之类的题,和这个题没关系)
这个题须要对搜索顺序进行排序,以我们人类思维来做数独的话。我们通常会先填那些可选择的数字少的空,再填可选择的数字多的空。由于先填可选择的数字少的空。就能对后面可选择数字多的空产生限制条件,这个不难理解。从搜索树的角度来讲。这样做,可以让靠近搜索树根的结点儿子少,远离树根的结点儿子多。加上剪枝,剪相同一个枝能少搜索非常多点,效果非常好,这样可以提高搜索速度。
有人说这就是启示式搜索了,我觉得调整搜索顺序仅仅能算作剪枝
思路:首先记录下每行每列有数字的格子个数,然后依据有数字的格子个数。对行、列进行降序排序,再依照这个排序顺序,不断寻找有空格的格子,将它增加搜索顺序表中,DFS过程就是对于搜索顺序表中的每一个格子。依次尝试不同的数字。同一时候须要加上剪枝:仅仅尝试同一行、同一列、同一小方格没有出现过的数字,这样做也能大大优化搜索效率。
当然还有还有一种做法DLX(Dancing Links),这样的做法就是Wikioi所说的启示式搜索了,速度非常快。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm> #define MAXN 100
#define MAXNUM 10 using namespace std; int belong[MAXN][MAXN]={ //belong[i][j]=(i,j)所属的方格
{0,0,0,0,0,0,0,0,0,0},
{0,1,1,1,2,2,2,3,3,3},
{0,1,1,1,2,2,2,3,3,3},
{0,1,1,1,2,2,2,3,3,3},
{0,4,4,4,5,5,5,6,6,6},
{0,4,4,4,5,5,5,6,6,6},
{0,4,4,4,5,5,5,6,6,6},
{0,7,7,7,8,8,8,9,9,9},
{0,7,7,7,8,8,8,9,9,9},
{0,7,7,7,8,8,8,9,9,9}
};
int score[MAXN][MAXN]={ //score[i][j]=(i,j)格子相应的权值
{0,0,0,0,0,0,0,0,0,0},
{0,6,6,6,6,6,6,6,6,6},
{0,6,7,7,7,7,7,7,7,6},
{0,6,7,8,8,8,8,8,7,6},
{0,6,7,8,9,9,9,8,7,6},
{0,6,7,8,9,10,9,8,7,6},
{0,6,7,8,9,9,9,8,7,6},
{0,6,7,8,8,8,8,8,7,6},
{0,6,7,7,7,7,7,7,7,6},
{0,6,6,6,6,6,6,6,6,6},
{0,0,0,0,0,0,0,0,0,0},
}; int map[MAXN][MAXN]; //map[][]保存数独棋盘
int xVisit[MAXN][MAXNUM]; //xVisit[i][j]=1表示行i上数字j已经用过
int yVisit[MAXN][MAXNUM]; //yVisit[i][j]=1表示列i上数字j已经用过
int squareVisit[MAXN][MAXNUM]; //squareVisit[i][j]=1表示在小九宫格i中数字J用过
int xPermutation[MAXN]; //行的搜索顺序
int yPermutation[MAXN]; //列的搜索顺序
int sortX[MAXN],sortY[MAXN];
int hasNumOnX[MAXN]; //每一行有数字的格子个数
int hasNumOnY[MAXN]; //每一列有数字的格子个数
int blankNum=0; //空白格子个数
int maxScore=-1; //最大分数 bool XCmp(int i,int j) //对行排序的比較函数
{
return hasNumOnX[sortX[i]]>hasNumOnX[sortX[j]];
} bool YCmp(int i,int j) //对列排序的比較函数
{
return hasNumOnY[sortY[i]]>hasNumOnY[sortY[j]];
} void getPermutation() //对搜索顺序。依据行/列上有数字的个数降序排序。先搜索有数字多的
{
sort(sortX+1,sortX+9+1,XCmp);
sort(sortY+1,sortY+9+1,YCmp);
for(int i=1;i<=9;i++)
for(int j=1;j<=9;j++)
if(!map[sortX[i]][sortY[j]]) //空白格子
{
xPermutation[blankNum]=sortX[i]; //在搜索顺序中记录下它
yPermutation[blankNum]=sortY[j];
blankNum++;
}
} void refreshScore() //更新最大分数
{
int sum=0;
for(int i=1;i<=9;i++)
for(int j=1;j<=9;j++)
sum+=score[i][j]*map[i][j];
if(maxScore<sum) maxScore=sum;
} void dfs(int step) //正在填搜索顺序中的第step个空格,step属于[0,blankNum)
{
if(step==blankNum) //全部格子都填完了
{
refreshScore(); //更新答案
return;
}
int nowx=xPermutation[step];
int nowy=yPermutation[step];
if(map[nowx][nowy]) //这个格子已经被填过了
return;
for(int num=1;num<=9;num++) //枚举数字num
{
if(!xVisit[nowx][num]&&!yVisit[nowy][num]&&!squareVisit[belong[nowx][nowy]][num]) //数字num没有被用过
{
xVisit[nowx][num]=true;
yVisit[nowy][num]=true;
squareVisit[belong[nowx][nowy]][num]=true; //标记这个数字已经用过
map[nowx][nowy]=num;
dfs(step+1);
xVisit[nowx][num]=false;
yVisit[nowy][num]=false;
squareVisit[belong[nowx][nowy]][num]=false; //恢复原样,标记这个数字没有被用过
map[nowx][nowy]=0;
}
}
} int main()
{
for(int i=1;i<=9;i++)
{
sortX[i]=i;
sortY[i]=i;
for(int j=1;j<=9;j++)
{
scanf("%d",&map[i][j]);
if(map[i][j])
{
xVisit[i][map[i][j]]=true;
yVisit[j][map[i][j]]=true;
squareVisit[belong[i][j]][map[i][j]]=true;
hasNumOnX[i]++;
hasNumOnY[j]++;
}
}
}
getPermutation(); //对搜索顺序进行排序
dfs(0);
printf("%d\n",maxScore);
system("pause");
return 0;
}
6、Wikioi 2808 二的幂次方
题目描写叙述 Description
不论什么一个正整数都能够用2的幂次方表示.
比如:137=2^7+2^3+2^0
同一时候约定次方用括号来表示,即a^b可表示为a(b)
由此可知,137可表示为:2(7)+2(3)+2(0)
进一步:7=2^2+2+2^0 (2^1用2表示)
3=2+2^0
所以最后137可表示为:2(2(2)+2+2(0))+2(2+2(0))+2(0)
又如:1315=2^10+2^8+2^5+2+1
所以1315最后可表示为:2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0)
输入描写叙述 Input Description
正整数n
输出描写叙述 Output Description
符合约定的n的0,2表示(在表示中不能有空格)
例子输入 Sample Input
【输入例子1】
137
【输入例子2】
1315
例子输出 Sample Output
【输出例子1】
2(2(2)+2+2(0))+2(2+2(0))+2(0)
【输出例子2】
2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0)
数据范围及提示 Data Size & Hint
n为2的指数<=1100586419200
非常恶心的搜索题。裸DFS加上变态的模拟。刷尿我了
#include <stdio.h>
#include <stdlib.h>
#include <string.h> #define LEN 64 void dfs(long long int n) //将n拆成2的幂次方
{
bool flag=false; //若flag=true,则表明是括号后第一个数字
for(long long int i=0;i<LEN;i++)
{
if(n<0)
{
if(flag) //不是括号后第一个数字
printf("+"); //须要打印加号
else flag=true;
if(LEN-i-1==1) //2^1,不必往下递归也不必输出括号
printf("2");
else
{
printf("2("); //先输出左边的括号
if(LEN-i-1==0) //2^0,直接输出0
printf("0");
else dfs(LEN-i-1); //否则,2^x,x>1,继续搜索
printf(")");
}
}
n=n<<1;
}
}
int main()
{
long long int n;
scanf("%lld",&n);
dfs(n);
printf("\n");
return 0;
}
7、POJ 2488 A Knight's Journey
Description
The knight is getting bored of seeing the same black and white squares again and again and has decided to make a journey
around the world. Whenever a knight moves, it is two squares in one direction and one square perpendicular to this. The world of a knight is the chessboard he is living on. Our knight lives on a chessboard that has a smaller area than a regular 8 * 8 board,
but it is still rectangular. Can you help this adventurous knight to make travel plans?
Problem
Find a path such that the knight visits every square once. The knight can start and end on any square of the board.
Input
p * q chessboard, where p describes how many different square numbers 1, . . . , p exist, q describes how many different square letters exist. These are the first q letters of the Latin alphabet: A, . . .
Output
chessboard with knight moves followed by an empty line. The path should be given on a single line by concatenating the names of the visited squares. Each square name consists of a capital letter followed by a number.
If no such path exist, you should output impossible on a single line.
Sample Input
3
1 1
2 3
4 3
Sample Output
Scenario #1:
A1 Scenario #2:
impossible Scenario #3:
A1B3C1A2B4C2A3B1C3A4B2C4
Source
今天整个人状态像坨翔一样。。
。
这个题就是个简单的DFS。仅仅只是要求输出的遍历方案为字典序最小的,这不难实现,仅仅须要在搜索时依照下图这种顺序依次走即可。看了这图就能明确的
另外一定要注意:假设全部格子当成起点都尝试一遍行不通以后,一定要输出impossible,否则会WA!
#include <iostream>
#include <string>
#include <stdio.h>
#include <string.h>
#include <stdlib.h> using namespace std; int xx[]={-1,1,-2,2,-2,2,-1,1},yy[]={-2,-2,-1,-1,1,1,2,2};
bool hasVisit[30][30];
int n,m,depth=0; //棋盘大小n*m
string move; //马的遍历顺序 bool isAllVisited() //检查是否全部点都被遍历过
{
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(!hasVisit[i][j])
return false;
return true;
} bool inMap(int x,int y)
{
if(x<1||x>n||y<1||y>m) return false;
return true;
} bool dfs(int x,int y,int step,int Case) //马的位置是(x,y)
{
hasVisit[x][y]=true;
move+=y-1+'A';
move+=x-1+'1';
if(step==depth) //全部点都被訪问过了
{
cout<<"Scenario #"<<Case<<":"<<endl<<move<<endl<<endl;
return true;
}
for(int dir=0;dir<8;dir++)
{
int newx=x+xx[dir],newy=y+yy[dir];
if(!inMap(newx,newy)) continue; //越界了
if(hasVisit[newx][newy]) continue; //訪问过了
string cpy=move;
if(dfs(newx,newy,step+1,Case)) return true;
move=cpy;
}
hasVisit[x][y]=false;
return false;
} int main()
{
int testCase;
scanf("%d",&testCase);
for(int Case=1;Case<=testCase;Case++)
{
memset(hasVisit,false,sizeof(hasVisit));
move="";
scanf("%d%d",&n,&m);
if(n==1&&m==1)
{
printf("Scenario #%d:\nA1\n\n",Case);
continue;
}
if(n*m>26||n>8||m>8||n<=2||m<=2)
{
printf("Scenario #%d:\nimpossible\n\n",Case);
continue;
}
depth=n*m;
bool flag=true;
for(int i=1;i<=n&&flag;i++)
for(int j=1;j<=m;j++)
{
if(dfs(i,j,1,Case))
{
flag=false;
break;
}
}
if(flag)
printf("Scenario #%d:\nimpossible\n\n",Case);
}
return 0;
}
二、广度优先搜索
1、Wikioi 1004 四子连棋
题目描写叙述 Description
在一个4*4的棋盘上摆放了14颗棋子,当中有7颗白色棋子,7颗黑色棋子,有两个空白地带,不论什么一颗黑白棋子都能够向上下左右四个方向移动到相邻的空格。这叫行棋一步,黑白两方交替走棋,随意一方能够先走,假设某个时刻使得随意一种颜色的棋子形成四个一线(包含斜线)。这种状态为目标棋局。
● ○ ● ○ ● ○ ● ● ○ ● ○ ○ ● ○
输入描写叙述 Input Description
从文件里读入一个4*4的初始棋局,黑棋子用B表示。白棋子用W表示,空格地带用O表示。
输出描写叙述 Output Description
用最少的步数移动到目标棋局的步数。
例子输入 Sample Input
BWBO
WBWB
BWBW
WBWO
例子输出 Sample Output
5
这是一道非常经典的广搜题。须要运用判重的知识。广搜过程应该非常好理解,就是每次取出队首的状态,在队首的状态棋盘中找到两个空格。并将空格和相邻的棋子交换,要注意这里有先手和后手之分,BFS的状态应该包括棋盘、搜索步数、哈希值和近期下的棋的颜色,近期下的是白色,那么空格仅仅能和黑棋交换,否则空格仅仅能和白棋交换。判重也是一样。对于状态(s,最后下的是黑棋)和(s,最后下的是白棋)两种状态来说,尽管棋盘数组是一样的。可是最后下的棋颜色不同,终于的结果也会不同,因此判重数组应该是两维的:第一维是棋盘的哈希值,第二维是棋盘的最后下的棋的颜色,另外要注意,假设用三进制表示棋盘的哈希值,棋盘的哈希值<=3^16,这个范围明显超出了int表达范围,因此须要用Map容器保存棋盘哈希值这一个维度,也能够用string类型保存这个哈希值,也许会简单非常多,可是要牺牲一点空间。假设想要空间不要时间。也能够用康托展开去保存哈希值,写起来复杂非常多。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#include <map> #define MAXQ 100000
#define MAXN 1000000 using namespace std; map<int,bool>inQueue[4]; struct node
{
int step; //移动步数(BFS深度)
int hash; //棋盘哈希值(三进制转十进制后的数)
int map[6][6];
int last; //最后一次下棋的是白还是黑,last=1表示黑,last=2表示白
}first,q[MAXQ]; int h=0,t=1;
//bool inQueue[MAXN][4]; //inQueue[s][1]表示黑子最后下,状态为s的情况在队列里,inQueue[s][2]表示白字最后下,状态为s的情况在队列里
int xx[]={1,-1,0,0},yy[]={0,0,1,-1}; int getHashFromArray(node status) //获取棋盘状态的哈希值
{
int sum=0;
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++)
{
sum*=3;
sum+=status.map[i][j];
}
return sum;
} bool check(node status) //检查状态status是否符合要求
{
int flag=0;
for(int i=1;i<=4;i++)
{
int j;
for(j=2;j<=4;j++)
if(status.map[i][j]!=status.map[i][j-1])
break;
if(j>4) return true;
}
for(int j=1;j<=4;j++)
{
int i;
for(i=2;i<=4;i++)
if(status.map[i][j]!=status.map[i-1][j])
break;
if(i>4) return true;
}
if(status.map[1][1]==status.map[2][2]&&status.map[2][2]==status.map[3][3]&&status.map[3][3]==status.map[4][4])
return true;
if(status.map[1][4]==status.map[2][3]&&status.map[2][3]==status.map[3][2]&&status.map[3][2]==status.map[4][1])
return true;
return false;
} bool inMap(int x,int y)
{
if(x<0||x>4||y<0||y>4) return false;
return true;
} int bfs()
{
//Case1:先手黑棋
first.step=0;
first.last=1;
first.hash=getHashFromArray(first); //获取初始棋盘的哈希值
q[h]=first;
inQueue[first.last][first.hash]=true;
//Case2:先手白棋
first.last=2;
q[t++]=first;
inQueue[first.last][first.hash]=true;
//BFS过程
while(h<t)
{
node now=q[h++]; //队首出列
inQueue[now.last][now.hash]=false;
if(check(now)) //符合题目的目标要求,则返回答案
return now.step;
for(int x=1;x<=4;x++) //寻找空格坐标(x,y)
for(int y=1;y<=4;y++)
{
if(now.map[x][y]) continue;
for(int dir=0;dir<4;dir++) //四个方向移动空格
{
int newx=x+xx[dir],newy=y+yy[dir];
if(!inMap(newx,newy)) continue; //越界了
if(now.map[newx][newy]==now.last) //(newx,newy)的颜色和近期下的棋子颜色一样。不能让空格往这移动,黑白棋要轮流下
continue;
node next=now;
next.step++;
next.last=3-next.last; //这一局下的棋和上一局颜色相反
swap(next.map[x][y],next.map[newx][newy]);
next.hash=getHashFromArray(next);
if(!inQueue[next.last][next.hash]) //该状态没有被訪问过
{
q[t++]=next;
inQueue[next.last][next.hash]=true;
}
}
}
}
} int main()
{
char s[10];
for(int i=1;i<=4;i++)
{
scanf("%s",s+1);
for(int j=1;j<=4;j++)
{
if(s[j]=='B') first.map[i][j]=1;
else if(s[j]=='W') first.map[i][j]=2;
}
}
printf("%d\n",bfs());
return 0;
}
2、Wikioi 1026 逃跑的拉尔夫
题目描写叙述 Description
年轻的拉尔夫开玩笑地从一个小镇上偷走了一辆车,但他没想到的是那辆车属于警察局,而且车上装实用于发射车子移动路线的装置。
那个装置太旧了,以至于仅仅能发射关于那辆车的移动路线的方向信息。
编敲代码,通过使用一张小镇的地图帮助警察局找到那辆车。程序必须能表示出该车终于全部可能的位置。
小镇的地图是矩形的,上面的符号用来标明哪儿能够行车哪儿不行。“.”表示小镇上那块地方是能够行车的,而符号“X”表示此处不能行车。
拉尔夫所开小车的初始位置用字符的“*”表示。且汽车能从初始位置通过。
汽车能向四个方向移动:向北(向上),向南(向下)。向西(向左)。向东(向右)。
拉尔夫所开小车的行动路线是通过一组给定的方向来描写叙述的。在每一个给定的方向,拉尔夫驾驶小车通过小镇上一个或很多其它的可行车地点。
输入描写叙述 Input Description
输入文件的第一行包括两个用空格隔开的自然数R和C,1≤R≤50,1≤C≤50。分别表示小镇地图中的行数和列数。
下面的R行中每行都包括一组C个符号(“.”或“X”或“*”)用来描写叙述地图上对应的部位。
接下来的第R+2行包括一个自然数N,1≤N≤1000,表示一组方向的长度。
接下来的N行幅行包括下述单词中的任一个:NORTH(北)、SOUTH(南)、WEST(西)和EAST(东),表示汽车移动的方向,不论什么两个连续的方向都不同样。
输出描写叙述 Output Description
输出文件应包括用R行表示的小镇的地图(象输入文件里一样),字符“*”应该仅用来表示汽车终于可能出现的位置。
例子输入 Sample Input
4 5
.....
.X...
...*X
X.X..
3
NORTH
WEST
SOUTH
例子输出 Sample Output
.....
*X*..
*.*.X
X.X..
这个题也是广搜题,由于汽车正在行驶时的方向对终于结果有影响。所以BFS的状态应该是一个三元组(x,y,n)。表示汽车当前坐标位于(x,y),正在以第n种方向行驶,判重数组也是三维的。BFS过程中状态转换时,就沿着第n种方向不断前进直到有障碍物。其间每经过一个点(newx,newy)。就将状态(newx,newy,n+1)入队,BFS循环每次開始时,将队首出队,若队首正在行驶的方向大于n,则说明全部的方向都行驶完了,在地图上记录下汽车的终于位置后跳过。记住是跳过!
由于汽车的终止位置不止一个
#include <stdio.h>
#include <string.h>
#include <stdlib.h> #define MAXN 60
#define MAXM 1100
#define MAXQ 1000000 struct node
{
int NumOfDir; //当前正在用的方向
int x,y; //当前车子坐标
}first,q[MAXQ]; int h=0,t=1;
int map[MAXN][MAXN],R,C,sx,sy,n; //map[i][j]=1表示障碍物,2表示车终于可能出现的位置,初始坐标(sx,sy)
int direct[MAXM]; //
int xx[]={-1,1,0,0},yy[]={0,0,-1,1};
bool inQueue[MAXN][MAXN][MAXM]; //判重用数组,inQueue[i][j][n]=true表示车子在(i,j),正在用第n个方向的状态在队列里 bool inMap(int x,int y) //判定(x,y)是否在棋盘内
{
if(x<1||x>R||y<1||y>C) return false;
return true;
} void bfs()
{
inQueue[first.x][first.y][first.NumOfDir]=true;
q[h]=first; //初始状态入队
while(h<t)
{
node now=q[h++]; //队首出队
inQueue[now.x][now.y][now.NumOfDir]=false;
if(now.NumOfDir>n) //全部的方向都用完了
{
map[now.x][now.y]=2;
continue;
}
node next=now;
next.NumOfDir++;
while(1)
{
next.x+=xx[direct[now.NumOfDir]];
next.y+=yy[direct[now.NumOfDir]];
if(map[next.x][next.y]||!inMap(next.x,next.y)) break;
if(!inQueue[next.x][next.y][next.NumOfDir])
{
q[t++]=next;
inQueue[next.x][next.y][next.NumOfDir]=true;
}
}
}
} int main()
{
char s[MAXN];
scanf("%d%d",&R,&C);
for(int i=1;i<=R;i++)
{
scanf("%s",s+1);
for(int j=1;j<=C;j++)
{
if(s[j]=='X') map[i][j]=1;
else if(s[j]=='*')
{
sx=i;
sy=j;
}
}
}
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%s",s);
if(s[0]=='N') direct[i]=0;
if(s[0]=='S') direct[i]=1;
if(s[0]=='W') direct[i]=2;
if(s[0]=='E') direct[i]=3;
}
first.x=sx,first.y=sy;
first.NumOfDir=1;
bfs();
for(int i=1;i<=R;i++)
{
for(int j=1;j<=C;j++)
{
if(map[i][j]==0) printf(".");
if(map[i][j]==1) printf("X");
if(map[i][j]==2) printf("*");
}
printf("\n");
}
return 0;
}
3、POJ 3278 Catch that cow
Description
Farmer John has been informed of the location of a fugitive cow and wants to catch her immediately. He starts at a pointN (0 ≤N ≤ 100,000) on a number line and the cow is at a pointK (0 ≤K ≤ 100,000) on the same number
line. Farmer John has two modes of transportation: walking and teleporting.
* Walking: FJ can move from any point X to the points X - 1 orX+ 1 in a single minute
* Teleporting: FJ can move from any point X to the point 2 × X in a single minute.
If the cow, unaware of its pursuit, does not move at all, how long does it take for Farmer John to retrieve it?
Input
Output
Sample Input
5 17
Sample Output
4
Hint
Source
USACO的题太SB了。所以这题就不细说了,注意检查是否越界,其它没什么了。裸BFS。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <queue> #define MAXN 101000 using namespace std; bool inQueue[MAXN];
int n,k; struct node
{
int pos,step;
}first; queue<node>q; bool inMap(int x)
{
if(x<0||x>100000) return false;
return true;
} void bfs()
{
q.push(first);
inQueue[first.pos]=true;
while(!q.empty())
{
node now=q.front();
q.pop();
if(now.pos==k)
{
printf("%d\n",now.step);
return;
}
if(inMap(now.pos*2))
if(!inQueue[now.pos*2])
{
node next=now;
next.pos*=2;
next.step+=1;
inQueue[next.pos]=true;
q.push(next);
}
if(inMap(now.pos+1))
if(!inQueue[now.pos+1])
{
node next=now;
next.pos+=1;
next.step+=1;
inQueue[next.pos]=true;
q.push(next);
}
if(inMap(now.pos-1))
if(!inQueue[now.pos-1])
{
node next=now;
next.pos-=1;
next.step+=1;
inQueue[next.pos]=true;
q.push(next);
}
}
} int main()
{
scanf("%d%d",&n,&k);
first.pos=n;
first.step=0;
bfs();
return 0;
}
4、Wikioi 1225 八数码难题
题目描写叙述 Description
Yours和zero在研究A*启示式算法.拿到一道经典的A*问题,可是他们不会做,请你帮他们.
问题描写叙述
在3×3的棋盘上,摆有八个棋子,每一个棋子上标有1至8的某一数字。棋盘中留有一个空格。空格用0来表示。空格周围的棋子能够移到空格中。
要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
输入描写叙述 Input Description
输入初试状态,一行九个数字,空格用0表示
输出描写叙述 Output Description
仅仅有一行。该行仅仅有一个数字。表示从初始状态到目标状态须要的最少移动次数(測试数据中无特殊无法到达目标状态数据)
例子输入 Sample Input
283104765
例子输出 Sample Output
4
按理说这个题应该算作A*或者IDA*的题,只是Wikioi的数据太弱了,BFS暴力也能过。仅仅能呵呵了。POJ爱卡常数,相同的代码在POJ没办法过
#include <iostream>
#include <string>
#include <cstdio>
#include <map>
#include <queue>
#include <stdlib.h> #define MAXN 12 using namespace std; struct node
{
string Permutation;
string move; //移动方式
int step;
node(){step=0; Permutation.clear(); move.clear();}
}first; map<string,int>visit;
queue<node>q; char status[MAXN][MAXN],CharofMove[]="udlr";
int xx[]={-1,1,0,0},yy[]={0,0,-1,1};
string End="123804765"; bool inMap(int a,int b) //推断(a,b)是否越界
{
if(a<1||a>3||b<1||b>3) return false;
return true;
} string GetPermutationFromArray() //数组转字符串
{
string ans;
int cnt=-1;
for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
ans+=status[i][j];
return ans;
} void GetArrayFromPermutation(string s) //字符串转数组
{
int cnt=-1;
for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
status[i][j]=s[++cnt];
} void bfs()
{
while(!q.empty()) q.pop();
q.push(first);
while(!q.empty())
{
node now=q.front();
q.pop();
if(visit[now.Permutation]) continue;
visit[now.Permutation]=1;
if(now.Permutation==End)
{
cout<<now.step<<endl;
return;
}
GetArrayFromPermutation(now.Permutation); //获得当前的棋盘状态
int bx,by; //空格坐标(bx,by)
for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
{
if(status[i][j]=='0')
bx=i,by=j;
}
for(int dir=0;dir<4;dir++)
{
int newx=bx+xx[dir],newy=by+yy[dir];
if(!inMap(newx,newy)) continue;
swap(status[newx][newy],status[bx][by]);
node temp=now;
temp.step++;
temp.move+=CharofMove[dir];
temp.Permutation=GetPermutationFromArray();
q.push(temp);
swap(status[newx][newy],status[bx][by]);
}
}
} int main()
{
int cnt=0;
string start="";
char in[20];
cin>>in;
for(int i=1;i<=3;i++)
{
for(int j=1;j<=3;j++)
{
status[i][j]=in[cnt++];
}
}
first.Permutation=GetPermutationFromArray();
bfs();
return 0;
}
三、迭代加深搜索
题目描写叙述 Description
有一个5×5的棋盘,上面有一些格子被染成了黑色,其它的格子都是白色,你的任务的对棋盘一些格子进行染色,使得全部的黑色格子能连成一块,而且你染色的格子数目要最少。读入一个初始棋盘的状态,输出最少须要对多少个格子进行染色,才干使得全部的黑色格子都连成一块。
(注:连接是指上下左右四个方向,假设两个黑色格子仅仅共同拥有一个点。那么不算连接)
输入描写叙述 Input Description
输入包含一个5×5的01矩阵。中间无空格。1表示格子已经被染成黑色。
输出描写叙述 Output Description
输出最少须要对多少个格子进行染色
例子输入 Sample Input
11100
11000
10000
01111
11111
例子输出 Sample Output
1
这个题因为数据范围不大(棋盘5*5),因此深度最大不超过25(搜索深度等于要染色的格子数),而这个题又是要求最少步数,因此能够使用迭代加深搜索,迭代加深搜索的细节不再赘述。以下看下这个题须要注意的一些细节:
1、每次搜索从棋盘左上角(1,1)開始,搜索状态为三元组(x,y,step),表示当前已经搜索到点(x,y),在深度step,搜索过程中寻找要么与step同一行。在step后面的白色格子染色,要么寻找比step行数大的格子。考虑下图的棋盘状况:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXBzd3d3dw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center">
当当前搜索格子位于点A(例如以下图时),这一步的搜索能够转移到绿色格子的状态
而整个搜索从左上角的点(1,1)。深度为0開始,因此整个搜索顺序例如以下图
下图中黄色的格子将以箭头所看到的的顺序尝试下一步的DFS深搜操作
代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h> #define MAXN 100
#define next(x,y,step) (y==5?dfs(x+1,y,step):dfs(x,y+1,step)) using namespace std; int t[MAXN][MAXN],map[MAXN][MAXN],depth;
bool ans=0;
int xx[]={1,-1,0,0},yy[]={0,0,1,-1}; void del(int x,int y) //递归将与(x,y)相邻的格子都刷白
{
int newx,newy;
t[x][y]=0;
for(int dir=0;dir<4;dir++)
{
newx=x+xx[dir];
newy=y+yy[dir];
if(newx<1||newx>5||newy<1||newy>5||!t[newx][newy])
continue;
del(newx,newy);
}
} bool check() //检查全部的黑色格子是否连在一起
{
for(int i=1;i<=5;i++)
for(int j=1;j<=5;j++)
t[i][j]=map[i][j]; //将Map数组复制到t数组中。之后对t数组进行操作
bool flag=false;
for(int i=1;i<=5;i++)
for(int j=1;j<=5;j++)
if(t[i][j]) //找到了一个黑色格子
{
if(!flag) //第一块黑色区域
{
del(i,j); //将这一块都刷白
flag=true;
}
else return false; //找到新的黑色区域,则表明黑色区域有多块
}
return true;
} void dfs(int x,int y,int step) //由(x,y)開始搜索。找一个白格染成黑色,搜索步数为step
{
if(step==depth) //到达搜索深度
{
if(check()) //假设达到了目标状态,则找到了答案
ans=1;
return;
}
if(ans||x==6) return;
for(int i=y;i<=5;i++) //将本行的格子染色
{
if(map[x][i]) continue; //假设是黑格子就跳过
map[x][i]=1; //将这个白格子染黑
next(x,i,step+1);
map[x][i]=0; //回溯后恢复
}
for(int i=x+1;i<=5;i++)
{
for(int j=1;j<=5;j++)
{
if(map[i][j]) continue; //假设是黑格子就跳过
map[i][j]=1; //将这个白格子染黑
next(i,j,step+1);
map[i][j]=0; //回溯后恢复
}
}
return;
} int main()
{
char s[10];
for(int i=1;i<=5;i++)
{
scanf("%s",s+1);
for(int j=1;j<=5;j++)
map[i][j]=s[j]-'0';
}
for(depth=0;depth<=25;depth++)
{
dfs(1,1,0);
if(ans)
{
printf("%d\n",depth);
system("pause");
return 0;
}
}
system("pause");
return 0;
}
四、基于迭代加深的启示式搜索IDA*
1、POJ 4007 Flood-it!
Description
At the beginning of the game, system will randomly generate an N×N square board and each grid of the board is painted by one of the six colors. The player starts from the top left corner. At each step, he/she selects a color and changes all the grids connected
with the top left corner to that specific color. The statement “two grids are connected” means that there is a path between the certain two grids under condition that each pair of adjacent grids on this path is in the same color and shares an edge. In this
way the player can flood areas of the board from the starting grid (top left corner) until all of the grids are in same color. The following figure shows the earliest steps of a 4×4 game (colors are labeled in 0 to 5):
Given a colored board at very beginning, please find the minimal number of steps to win the game (to change all the grids into a same color).
Input
The following N lines show an N×N matrix (ai,j)n×n representing the game board. ai,j is in the range of 0 to 5 representing the color of the corresponding grid.
The input ends with N = 0.
Output
Sample Input
2
0 0
0 0
3
0 1 2
1 1 2
2 2 1
0
Sample Output
0
3
Source
然后每次搜索时。对棋盘尝试6种颜色的flood操作(flood操作用DFS实现),对每种能够flood的颜色,flood整个棋盘,然后向下继续搜索。直到到达指定搜索深度depth,推断棋盘是否已经全然连通。
第二张图是IDA*时,以与连通块相邻的点为起点进行flood操作的搜索方向
#include <stdio.h>
#include <string.h>
#include <stdlib.h> #define MAXN 10 int map[MAXN][MAXN],status[MAXN][MAXN];
//map数组保存棋盘。status[i][j]=1表示(i,j)在联通块内。2表示(i,j)不在联通块但和联通块相邻,0表示(i,j)不和联通块相邻
int N,xx[]={1,-1,0,0},yy[]={0,0,1,-1},ans,depth; bool inMap(int x,int y) //判定(x,y)是否越界
{
if(x<1||x>N||y<1||y>N)
return false;
return true;
} void flood(int x,int y,int color) //以(x,y)为起点、color色的格子进行flood操作
{
status[x][y]=1;
for(int dir=0;dir<4;dir++)
{
int newx=x+xx[dir],newy=y+yy[dir];
if(!inMap(newx,newy)) continue;
if(status[newx][newy]==1) continue; //在联通块内。跳过
if(map[newx][newy]==color) //相邻格子(newx,newy)是color色且和联通块相邻
flood(newx,newy,color);
else //相邻格子(newx,newy)不和联通块相邻。标记它为和联通块相邻
status[newx][newy]=2;
}
} int h() //估价函数,h()=当前状态下棋盘中的颜色种类数,也就是至少要染h()次
{
int sum=0;
bool flag[6];
memset(flag,false,sizeof(flag));
for(int i=1;i<=N;i++)
for(int j=1;j<=N;j++)
if(!flag[map[i][j]]&&status[i][j]!=1)
{
flag[map[i][j]]=true;
sum++;
}
return sum;
} int getCnt(int color) //对剩余的格子染color色,返回最多染色的个数(推断颜色color能否够染)
{
int sum=0;
for(int i=1;i<=N;i++)
for(int j=1;j<=N;j++)
{
if(map[i][j]==color&&status[i][j]==2) //找到一个与联通块相邻且为color色的格子
{
flood(i,j,color);
sum++;
}
}
return sum;
} bool IDAstar(int step) //IDA*迭代深搜
{
if(step==depth) //到达指定深度,且棋盘颜色所有一样
return h()==0;
if(step+h()>depth) return false; //假设估计要走的步数比指定深度大。不用接着深搜了
for(int color=0;color<6;color++)
{
int cpy[MAXN][MAXN];
memcpy(cpy,status,sizeof(status));
if(!getCnt(color)) //全部格子都染不了色
continue;
if(IDAstar(step+1)) return true;
memcpy(status,cpy,sizeof(cpy));
}
return false;
} int main()
{
while(1)
{
memset(status,0,sizeof(status));
memset(map,0,sizeof(map));
scanf("%d",&N);
if(!N) return 0;
for(int i=1;i<=N;i++)
for(int j=1;j<=N;j++)
scanf("%d",&map[i][j]);
flood(1,1,map[1][1]);
depth=h();
while(1)
{
if(IDAstar(0))
break;
depth++;
}
printf("%d\n",depth);
}
return 0;
}
五、基于BFS的启示式搜索A*
六、双向广度优先搜索DBFS
七、优先队列BFS
1、POJ 2908 Quantum
Description
At the Institution for Bits and Bytes at University of Ramville, Prof. Jeremy Longword and his eight graduate students are investigating a brand new way of storing and manipulating data on magnetic disks for use in hard drives. The method is based on letting
quasimagnetic quantum operations operate on the sectors on the disk, and is, of course, safer andmore reliable than any earlier invented storage method. The use of each quantum operation costs a certain amount of energy, and the more energy the storage unit
consumes, the warmer it will get. Therefore, you and your research team, are assigned the task of writing a program that, given sets of possible quantum operations and their costs, can calculate the lowest possible total cost for transforming a set of data
to the wanted result.
On the disk, binary words of length 1 ≤ L ≤ 20 are treated. The quantum operations are defined by strings of the same length as the binary words, and are built from the four lettersN (does nothing),F (inverts one bit),S
(sets a bit to 1), andC (resets a bit to 0). Each letter in the string corresponds to an operation on the bit in the binary word at the same position. The binary words are transformed one by one and the total energy cost for the transformation is
calculated as the sum of the costs for the performed quantum operations.
Input
The input starts with a single positive integer N ≤ 20 on a row, deciding the number of test cases that will follow. Then, for each of the test cases:
- One line containing three integers: L, nop andnw separated by one space.
- L indicates the length of the binary words and the quantum operations.
- nop (≤ 32) is the number of quantum operations that are available for use when transforming the binary words.
- nw (≤ 20) is the number of binary words that are to be transformed in the current test case.
After this, nop rows follows, each of them containing the definition of a quantum operation followed by the energy cost 0 ≤ci 1000 of carrying
out the quantum operation. The definition and the cost are separated by a single space.
Finally, there are nw rows, each containing two binary words separated by a single space. The first of these words should, when possible, be transformed to the second using the quantum operations. The binary words are expressed as sequences of 1’ s and 0’s.
After these rows, the next test case follows, if there is any.
Output
Each test case should produce a row containing a list of the energy costs of transforming each of the binary words. The costs should be separated by a single space and presented in the same order as the corresponding input. When there is no successful way
of transforming a binary word, “NP”, meaning not possible should be printed instead.
Sample Input
2
4 3 3
NFFN 1
NFNF 2
NNFN 4
0010 0100
0001 0010
0100 1000
4 4 5
CFSF 4
NNSS 3
FFFF 5
FNFN 6
1111 0000
1001 0110
0101 1000
1000 0011
0000 1001
Sample Output
1 3 NP
5 4 8 9 9
Source
以状态的花费为keyword构造一个降序的优先队列即可,每次从队首取出元素,依据题意模拟状态转移即可了,题目并不难,坑爹的是我把宏定义写错了。害得我一直在调试!
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <string>
#include <queue>
#include <map>
#include <iostream> #define mem(array,num) memset(array,num,sizeof(array))
#define MAXN 50
#define INF 0x3f3f3f3f using namespace std; struct node
{
int cost;
int num;
}first,end; bool operator<(const node&a,const node&b)
{
return a.cost>b.cost;
} //重载运算符,优先队列用 //map<string,bool>inQueue[MAXC]; //f数组用于检查当前状态是否已经经历过,若经历过,则它等于到达该状态的最少费用 priority_queue<node>pq; //优先队列 int L,n,m; //01串长度为L,n个操作,m个01串
int minCost[1<<22]; //minCost[i]=到达状态i所需的最少费用
int ans[MAXN];
int operation[MAXN][MAXN]; //operation[i]=第i次操作
int origin[MAXN]; //origin[i]=第i问的初始状态
int target[MAXN]; //target[i]=第i问的终止状态
int cost[MAXN]; int min(int a,int b)
{
if(a<b) return a;
return b;
} void Clear() //清空数组
{
mem(ans,-1);
mem(minCost,INF);
mem(origin,0);
mem(target,0);
} void bfs(int index)
{
int minn=INF,tmp;
while(!pq.empty()) pq.pop();
first.num=origin[index]; //初始状态
first.cost=0;
pq.push(first); //初始状态入队
while(!pq.empty())
{
node now=pq.top();
//cout<<now.num<<' '<<now.cost<<endl;
pq.pop();
if(now.num==target[index])
{
minn=min(minn,now.cost);
break;
}
for(int i=1;i<=n;i++)
{
tmp=now.num;
for(int j=0;j<L;j++)
{
int len=L-j-1;
if(operation[i][j]&1) //inverts one bit
tmp=tmp^(1<<len);
else if(operation[i][j]&2) //sets a bit to 1
tmp=tmp|(1<<len);
else if(operation[i][j]&4) //resets a bit to 0
tmp=tmp&(~(1<<len));
//cout<<tmp<<endl;
}
if(minCost[tmp]>now.cost+cost[i])
{
minCost[tmp]=now.cost+cost[i];
node next;
next.cost=now.cost+cost[i];
next.num=tmp;
pq.push(next);
}
}
}
if(minn!=INF)
ans[index]=minn;
return;
} int main()
{
int testCase;
scanf("%d",&testCase);
while(testCase--)
{
Clear();
char str[MAXN];
scanf("%d%d%d",&L,&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%s%d",str,&cost[i]);
for(int j=0;j<L;j++)
{
switch(str[j])
{
case 'N':operation[i][j]=0; break;
case 'F':operation[i][j]=1; break;
case 'S':operation[i][j]=2; break;
case 'C':operation[i][j]=4; break;
}
}
}
for(int i=1;i<=m;i++)
{
string start,endStatus;
cin>>start>>endStatus; //输入初始状态和终止状态
first.num=0;
end.num=0;
for(int j=0;j<L;j++)
{
if(start[j]=='1')
origin[i]|=(1<<L-j-1);
if(endStatus[j]=='1')
target[i]|=(1<<L-j-1);
}
}
for(int i=1;i<=m;i++)
{
first.cost=0;
mem(minCost,INF);
bfs(i);
}
for(int i=1;i<=m;i++)
{
if(ans[i]!=-1) printf("%d ",ans[i]);
else printf("NP ");
}
printf("\n");
}
return 0;
}
[NOIP 2014复习]第二章:搜索的更多相关文章
- [NOIP 2014复习]第三章:动态规划——NOIP历届真题回想
背包型动态规划 1.Wikioi 1047 邮票面值设计 题目描写叙述 Description 给定一个信封,最多仅仅同意粘贴N张邮票,计算在给定K(N+K≤40)种邮票的情况下(假定全部的邮票数量都 ...
- java编程基础复习-------第二章
一.标识符 java中标识符的命名规则: 以数字.字母.下划线和$符号组成:不能用数字开头:不能是java的关键字. 注意:不要用$命名标识符.习惯上,$只用在机器自动产生的源代码中. 二.关键字 1 ...
- Java程序设计(第二版)复习 第二章
1.Java使用Unicode字符集,一般用16位二进制表示一个字符.且Java中午sizeof关键字,因为所有基本数据类型长度是确定的,不依赖执行环境. 2. Java变量在声明时并没有分配内存,真 ...
- CSS3秘笈复习:第一章&第二章&第三章
第一章: 1.<cite>标签不仅可以将网页设置为斜体,还能给标题做上标记,使它便于被搜索引擎搜索到. 第二章: 1.import指令链接样式表: CSS本身有一种添加外部样式的方法:@i ...
- C++第二章复习与总结(思维导图分享)
在完成了第二章的学习后,为了便于日后的复习整理,我制作了一张思维导图,有需要的可以自取. 基本数据类型 基础类型在cppreference网站上有非常完备的介绍,我一句话两句话也说不清,具体网址我会给 ...
- 第三部分:Android 应用程序接口指南---第二节:UI---第九章 搜索
第9章 搜索 在android平台上搜索是一个核心的用户功能.无论内容位于设备或网络上,用户应该能够搜索任何对它们可用的数据.为了创建一个一致的用户搜索体验,Android平台提供了一个搜索框架帮助你 ...
- Unity 游戏框架搭建 2019 (十八~二十) 概率函数 & GameObject 显示、隐藏简化 & 第二章 小结与快速复习
在笔者刚做项目的时候,遇到了一个需求.第一个项目是一个跑酷游戏,而跑酷游戏是需要一条一条跑道拼接成的.每个跑道的长度是固定的,而怪物的出现位置也是在跑道上固定好的.那么怪物出现的概率决定一部分关卡的难 ...
- jQuery复习:第二章&第三章
第二章 一.选择器 1.层次选择器 $(“ancestor descendant”)选取ancestor元素里的所有后代元素 $(“parent > child”)选取parent元素下的chi ...
- php大力力 [016节] 兄弟连高洛峰php教程(2014年 14章数据库章节列表)
2015-08-25 php大力力016 兄弟连高洛峰php教程(2014年 14章数据库章节列表) [2014]兄弟连高洛峰 PHP教程14.1.1 复习数据库 15:58 [2014]兄弟连高洛 ...
随机推荐
- 执行次数最多的sql语句
执行次数最多的sql语句 * FROM ( creation_time N'语句编译时间' ,DB_NAME(st.dbid) AS dbname ,OBJECT_NAME(st.objectid) ...
- 查看Buffer Pool使用情况--[转]
----源自:微软官方博客论坛 我的SQL Server buffer pool很大,有办法知道是哪些对象吃掉我的buffer Pool内存么?比方说,能否知道是哪个数据库,哪个表,哪个index占用 ...
- spring各种邮件发送
参考地址一 参考地址二 参考地址三 参考地址四 Spring邮件抽象层的主要包为org.springframework.mail.它包括了发送电子邮件的主要接口MailSender,和值对象Simpl ...
- phpstorm设置断点调试
环境是:wamp PHP Version: 5.5.12 网上的教程很多,我自己按照教程操作,实现了断点调试,下面是我设置断点调试的步骤 1.修改配置文件php.ini,按下面修改(位置在最后) ; ...
- ASP.NET Web Forms 的 DI 應用範例
跟 ASP.NET MVC 与 Web API 比起来,在 Web Forms 应用程式中使用 Dependency Injection 要来的麻烦些.这里用一个范例来说明如何注入相依物件至 Web ...
- 使用Open Live Writer写博客
1. 下载安装软件 安装包路径http://openlivewriter.org/ 2.配置 打开软件后会提示你配置博客账号地址 3.安装代码高亮插件 下载插件源代码https://pan.baidu ...
- Vue2.0+Webpack项目环境构建到发布
前言:为什么要用webpack搭建项目呢,因为这个工具可以把目前浏览器不全部支持的ES6语法,通过打包工具生成所有浏览器都支持的单个JS文件. 参考: https://blog.csdn.net/u0 ...
- 细说WebSocket -- Node.js篇
在上一篇提高到了 web 通信的各种方式,包括 轮询.长连接 以及各种 HTML5 中提到的手段.本文将详细描述 WebSocket协议 在 web通讯 中的实现. 一.WebSocket 协议 1. ...
- Atitit.软件仪表盘(0)--软件的子系统体系说明
Atitit.软件仪表盘(0)--软件的子系统体系说明 1. 温度检测报警子系统 2. Os子系统 3. Vm子系统 4. Platform,业务系统子系统 5. Db数据库子系统 6. 通讯子系统 ...
- atitit。win7 win8 win9 win10 win11 新特性总结与战略规划
atitit.win7 win8 win9 win10 win11 新特性总结与战略规划 1. win7 1 1.1. 发布时间 2009年10月22日 1 1.2. 稳定性大幅提升,很少蓝屏死机 ...