[NOIP 2014复习]第二章:搜索

37 篇文章 1 订阅
24 篇文章 1 订阅

一、深度优先搜索(DFS)

1、Wikioi 1066引水入城

 

在一个遥远的国度,一侧是风景秀美的湖泊,另一侧则是漫无边际的沙漠。该国的行政 区划十分特殊,刚好构成一个N行M列的矩形,如上图所示,其中每个格子都代表一座城 市,每座城市都有一个海拔高度。 为了使居民们都尽可能饮用到清澈的湖水,现在要在某些城市建造水利设施。水利设施 有两种,分别为蓄水厂和输水站。蓄水厂的功能是利用水泵将湖泊中的水抽取到所在城市的 蓄水池中。因此,只有与湖泊毗邻的第1行的城市可以建造蓄水厂。而输水站的功能则是通 过输水管线利用高度落差,将湖水从高处向低处输送。故一座城市能建造输水站的前提,是 存在比它海拔更高且拥有公共边的相邻城市,已经建有水利设施。 由于第N行的城市靠近沙漠,是该国的干旱区,所以要求其中的每座城市都建有水利 设施。那么,这个要求能否满足呢?如果能,请计算最少建造几个蓄水厂;如果不能,求干 旱区中不可能建有水利设施的城市数目。

输入的每行中两个数之间用一个空格隔开。 输入的第一行是两个正整数N和M,表示矩形的规模。 接下来N行,每行M个正整数,依次代表每座城市的海拔高度。

输出有两行。如果能满足要求,输出的第一行是整数1,第二行是一个整数,代表最少 建造几个蓄水厂;如果不能满足要求,输出的第一行是整数0,第二行是一个整数,代表有 几座干旱区中的城市不可能建有水利设施。

2 5

9 1 5 4 3

8 7 6 1 2

1

1

【数据范围】 本题共有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 四色问题 

给定N(小于等于8)个点的地图,以及地图上各点的相邻关系,请输出用4种颜色将地图涂色的所有方案数(要求相邻两点不能涂成相同的颜色)

数据中0代表不相邻,1代表相邻

第一行一个整数n,代表地图上有n个点

接下来n行,每行n个整数,每个整数是0或者1。第i行第j列的值代表了第i个点和第j个点之间是相邻的还是不相邻,相邻就是1,不相邻就是0.

我们保证a[i][j] = a[j][i] (a[i,j] = a[j,i])

染色的方案数

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

15552

n<=8

 

题目太简单了就不多说了,数据太弱,很简单的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 虫食算

 所谓虫食算,就是原先的算式中有一部分被虫子啃掉了,需要我们根据剩下的数字来判定被啃掉的字母。来看一个简单的例子:

       43#9865#045
    +    8468#6633
       44445506978

    其中#号代表被虫子啃掉的数字。根据算式,我们很容易判断:第一行的两个数字分别是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个不同的字母分别代表的数字,使得该加法算式成立。输入数据保证有且仅有一组解,

输入包含4行。第一行有一个正整数N(N<=26),后面的3行每行有一个由大写字母组成的字符串,分别代表两个加数以及和。这3个字符串左右两端都没有空格,从高位到低位,并且恰好有N位。

  输出包含一行。在这一行中,应当包含唯一的那组解。解是这样表示的:输出N个数字,分别表示A,B,C……所代表的数字,相邻的两个数字用一个空格隔开,不能有多余的空格。

5
ABCED
BDACE
EBBAA

1 0 3 4 2

对于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、如果找到了答案,立刻停止搜索,因为搜索树中每一个结点的儿子很多,如果不停止搜索,还会继续搜索那些没有用的子树,这些部分非常大,会严重影响搜索速度。

总结:如果会高斯消元的话,我不建议考试时用爆搜做这个题,因为爆搜加剪枝不仅写起来麻烦,而且很容易因为剪枝写错了写漏了而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

N cities named with numbers 1 ... N are connected with one-way roads. Each road has two parameters associated with it : the road length and the toll that needs to be paid for the road (expressed in the number of coins).
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 N that he can afford with the amount of money he has.

Input

The first line of the input contains the integer K, 0 <= K <= 10000, maximum number of coins that Bob can spend on his way.
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

The first and the only line of the output should contain the total length of the shortest path from the city 1 to the city N whose total toll is less than or equal K coins.
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 靶形数独

小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他
们想用数独来一比高低。但普通的数独对他们来说都过于简单了,于是他们向Z 博士请教,
Z 博士拿出了他最近发明的“靶形数独”,作为这两个孩子比试的题目。
靶形数独的方格同普通数独一样,在 9 格宽×9 格高的大九宫格中有9 个3 格宽×3 格
高的小九宫格(用粗黑色线隔开的)。在这个大九宫格中,有一些数字是已知的,根据这些

数字,利用逻辑推理,在其他的空格上填入1 到9 的数字。每个数字在每个小九宫格内不能
重复出现,每个数字在每行、每列也不能重复出现。但靶形数独有一点和普通数独不同,即
每一个方格都有一个分值,而且如同一个靶子一样,离中心越近则分值越高。

 

上图具体的分值分布是:最里面一格(黄色区域)为 10 分,黄色区域外面的一圈(红
色区域)每个格子为9 分,再外面一圈(蓝色区域)每个格子为8 分,蓝色区域外面一圈(棕
色区域)每个格子为7 分,最外面一圈(白色区域)每个格子为6 分,如上图所示。比赛的
要求是:每个人必须完成一个给定的数独(每个给定数独可能有不同的填法),而且要争取
更高的总分数。而这个总分数即每个方格上的分值和完成这个数独时填在相应格上的数字
的乘积的总和。如图,在以下的这个已经填完数字的靶形数独游戏中,总分数为2829。游
戏规定,将以总分数的高低决出胜负。

由于求胜心切,小城找到了善于编程的你,让你帮他求出,对于给定的靶形数独,能
够得到的最高分数。

一共 9 行。每行9 个整数(每个数都在0—9 的范围内),表示一个尚未填满的数独方
格,未填的空格用“0”表示。每两个数字之间用一个空格隔开。

输出可以得到的靶形数独的最高分数。如果这个数独无解,则输出整数-1。

【输入输出样例 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

【输入输出样例 1】

2829

【输入输出样例 1】

2852

【数据范围】
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)

正整数n

符合约定的n的0,2表示(在表示中不能有空格)

【输入样例1】
137
【输入样例2】
1315

【输出样例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)

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

Background
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

The input begins with a positive integer n in the first line. The following lines contain n test cases. Each test case consists of a single line with two positive integers p and q, such that 1 <= p * q <= 26. This represents a 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

The output for every scenario begins with a line containing "Scenario #i:", where i is the number of the scenario starting at 1. Then print a single line containing the lexicographically first path that visits all squares of the 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

TUD Programming Contest 2005, Darmstadt, Germany

今天整个人状态像坨翔一样。。。这个题就是个简单的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 四子连棋

在一个4*4的棋盘上摆放了14颗棋子,其中有7颗白色棋子,7颗黑色棋子,有两个空白地带,任何一颗黑白棋子都可以向上下左右四个方向移动到相邻的空格,这叫行棋一步,黑白双方交替走棋,任意一方可以先走,如果某个时刻使得任意一种颜色的棋子形成四个一线(包括斜线),这样的状态为目标棋局。

 
 

 

从文件中读入一个4*4的初始棋局,黑棋子用B表示,白棋子用W表示,空格地带用O表示。

用最少的步数移动到目标棋局的步数。

BWBO
WBWB
BWBW
WBWO

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”表示此处不能行车。拉尔夫所开小车的初始位置用字符的“*”表示,且汽车能从初始位置通过。

汽车能向四个方向移动:向北(向上),向南(向下),向西(向左),向东(向右)。

拉尔夫所开小车的行动路线是通过一组给定的方向来描述的。在每个给定的方向,拉尔夫驾驶小车通过小镇上一个或更多的可行车地点。

输入文件的第一行包含两个用空格隔开的自然数R和C,1≤R≤50,1≤C≤50,分别表示小镇地图中的行数和列数。

以下的R行中每行都包含一组C个符号(“.”或“X”或“*”)用来描述地图上相应的部位。

接下来的第R+2行包含一个自然数N,1≤N≤1000,表示一组方向的长度。

接下来的N行幅行包含下述单词中的任一个:NORTH(北)、SOUTH(南)、WEST(西)和EAST(东),表示汽车移动的方向,任何两个连续的方向都不相同。

输出文件应包含用R行表示的小镇的地图(象输入文件中一样),字符“*”应该仅用来表示汽车最终可能出现的位置。

 

4 5

.....

.X...

...*X

X.X..

3

NORTH

WEST

SOUTH

 

.....

*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

Line 1: Two space-separated integers: N and K

Output

Line 1: The least amount of time, in minutes, it takes for Farmer John to catch the fugitive cow.

Sample Input

5 17

Sample Output

4

Hint

The fastest way for Farmer John to reach the fugitive cow is to move along the following path: 5-10-9-18-17, which takes 4 minutes.

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 八数码难题 

 

Yours和zero在研究A*启发式算法.拿到一道经典的A*问题,但是他们不会做,请你帮他们.
问题描述

在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。

输入初试状态,一行九个数字,空格用0表示

只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数(测试数据中无特殊无法到达目标状态数据)

283104765

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;
}


 

 

 

三、迭代加深搜索

有一个5×5的棋盘,上面有一些格子被染成了黑色,其他的格子都是白色,你的任务的对棋盘一些格子进行染色,使得所有的黑色格子能连成一块,并且你染色的格子数目要最少。读入一个初始棋盘的状态,输出最少需要对多少个格子进行染色,才能使得所有的黑色格子都连成一块。(注:连接是指上下左右四个方向,如果两个黑色格子只共有一个点,那么不算连接)

   输入包括一个5×501矩阵,中间无空格,1表示格子已经被染成黑色。

输出最少需要对多少个格子进行染色

11100

11000

10000

01111

11111

1

这个题由于数据范围不大(棋盘5*5),因此深度最大不超过25(搜索深度等于要染色的格子数),而这个题又是要求最少步数,因此可以使用迭代加深搜索,迭代加深搜索的细节不再赘述,下面看下这个题需要注意的一些细节:

1、每次搜索从棋盘左上角(1,1)开始,搜索状态为三元组(x,y,step),表示当前已经搜索到点(x,y),在深度step,搜索过程中寻找要么与step同一行,在step后面的白色格子染色,要么寻找比step行数大的格子,考虑下图的棋盘状况:

当当前搜索格子位于点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

Flood-it is a fascinating puzzle game on Google+ platform. The game interface is like follows:

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 input contains no more than 20 test cases. For each test case, the first line contains a single integer N (2<=N<=8) indicating the size of game board.

The following N lines show an N×N matrix (a i,j)n×n representing the game board. a i,j is in the range of 0 to 5 representing the color of the corresponding grid.

The input ends with N = 0.

Output

For each test case, output a single integer representing the minimal number of steps to win the game.

Sample Input

2
0 0 
0 0
3
0 1 2
1 1 2
2 2 1
0

Sample Output

0
3

Source

 
这个题要求出flood操作的最少步数,应该是个BFS的题,不过如果用BFS做,存储空间略大,而且更重要的是判重很麻烦,如果使用康托展开和逆康托展开,代码太长,用string保存又可能超时(POJ卡常数很厉害),由于题目范围小(N<=8),而且搜索深度小(一共只有6种颜色),所以可以采用IDA*来做。
IDA*的具体思路是,初始时定义搜索深度depth为初始棋盘的h()值,由于IDA*和A*的h()值一定小于等于真实步数值,所以没有问题,也许初始深度定为0也行。然后每次搜索时,对棋盘尝试6种颜色的flood操作(flood操作用DFS实现),对每种可以flood的颜色,flood整个棋盘,然后向下继续搜索,直到到达指定搜索深度depth,判断棋盘是否已经完全连通。
对于棋盘中连通块的表示,可以采取这样的方案:定义一个N*N的数组status,status[i][j]=1表示(i,j)在连通块中,2表示它与连通块相邻,3表示它与连通块不相邻,每次flood操作时,只对颜色相同、且与连通块相邻的点标记在连通块内,并以新点为起点继续flood操作,其他颜色不同的点,则标记它们与连通块相邻
 
如下图,黄色格子是连通块内的点,绿色格子是与连通块相邻的点,第一个图以(1,1)为起点进行IDA*前的第一次flood操作,它的flood方向如箭头所示(只有箭头指向的格子与黄色格子颜色相同才会被flood),在IDA*前进行flood操作,可以将与(1,1)颜色相同且相邻的点一次性地标记在连通块内,并标记出与连通块相邻的格子,防止之后IDA*因为之前没对格子进行标记,而搜不出答案的情况。

第二张图是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

以状态的花费为关键字构造一个降序的优先队列即可,每次从队首取出元素,根据题意模拟状态转移就行了,题目并不难,坑爹的是我把宏定义写错了,害得我一直在调试!

 

#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;
}

 

 


 

 



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值