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

一、深度优先搜索(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

 

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

 所谓虫食算,就是原先的算式中有一部分被虫子啃掉了,须要我们依据剩下的数字来判定被啃掉的字母。

来看一个简单的样例:

       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个不同的字母分别代表的数字。使得该加法算式成立。输入数据保证有且仅有一组解,

输入包括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、假设找到了答案。立马停止搜索,由于搜索树中每个结点的儿子非常多。假设不停止搜索,还会继续搜索那些没实用的子树,这些部分非常大,会严重影响搜索速度。

总结:假设会高斯消元的话。我不建议考试时用爆搜做这个题,由于爆搜加剪枝不仅写起来麻烦,并且非常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

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 Nthat 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 andK

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 (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

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

以状态的花费为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;
}

 

 


 

 



posted @ 2017-04-23 20:33  wzzkaifa  阅读(225)  评论(0编辑  收藏  举报