复健计划(1)之搜索(基础的搜索题目、模型和模板)

复健计划(1)之搜索(基础的搜索题目、模型和模板)

一、BFS
1、复健用题

AOJ 0558, POJ 3669, AOJ 0121还有ACwing上的FloodFill和最短路模型题目.

2、总结

总体来说,BFS适合求 最短路,最小步数 的问题, 这是DFS所不具备的, 但同时BFS搜索时状态 表示、扩展、判断 会相对麻烦,编写时需注意 细节和积累技巧 .

a) 以AOJ 0121为例,即类似八皇后问题的外部搜索问题,解决该类问题我认为有两点应当注意:
第一,优先采用 反向BFS ,即结果是永远不变的且已知的;从而从结果出发搜索出所有别的可能性,因为BFS的搜索顺序,再输出答案就是所求的最小步数;
第二,补充下细节,对于这种格子分布比较少的"类棋盘",变化规则可以简化,不必总用dx[4],dy[4];
具体实现代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>

using namespace std;

const int N=8;
map<string,int> mp;
int d[4]={-1,1,4,-4};

void bfs(){
	
	queue<string> q;
	q.push("01234567");
	
	while(q.size()){
		string t=q.front();
		q.pop();
		int p=t.find('0');
		for(int i=0;i<=3;i++){
			int np=p+d[i];
			if(p<0 || p>7 || ((p==3 || p==7) && i==1) || ((p==0 || p==4) && i==0)) continue;
			string n=t;
			swap(n[np],n[p]);
			if(mp[n]==0){
				mp[n]=mp[t]+1;
				q.push(n);
			}
		}
	}
	
}


int main(){
	bfs();
	mp["01234567"]=0;
	int a[N];
	while(cin>>a[0]>>a[1]>>a[2]>>a[3]>>a[4]>>a[5]>>a[6]>>a[7]){
		string s;
		for(int i=0;i<8;i++)
			s+=a[i]+'0';
		cout<<mp[s]<<endl;
	}
	return 0;
}

b) 剩下两道题目的基础模型都是"走迷宫型"最短路,并分别有一步加强;
AOJ 0558是 多步进行 最简单的BFS找最短路;这道题给我的启示就是别生硬的按照题目的要求来"走路",同时提供了一个很好的BFS最短路的模板(最短路别忘记v[][]数组)

代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

struct node{
	int x,y;
	int step;
};


const int N=1010;
int n,m,k,sx[10],sy[10];
int w[N][N];
bool v[N][N];
int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};
int ans;

void bfs(int ax,int ay,int bx,int by){
	
	queue<node> q;
	q.push({ax,ay,0});
	v[ax][ay]=1;
	
	while(q.size()){
		
		node t=q.front();
		q.pop();
		
		if(t.x==bx && t.y==by) {ans+=t.step;break;}
		
		for(int i=0;i<=3;i++){
			int nx=t.x+dx[i],ny=t.y+dy[i];
			if(v[nx][ny] || w[nx][ny]==0 || nx<1 || ny<1 || nx>n || ny>m) continue;
			v[nx][ny]=1;
			q.push({nx,ny,t.step+1});
		}
	}
}

int main(){
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++)	
		{
			char s[N];
			cin>>s;
			for(int j=0;j<strlen(s);j++){
				if(s[j]=='X') w[i][j+1]=0;
				else if(s[j]=='.') w[i][j+1]=10;
				else if(s[j]=='S') sx[0]=i,sy[0]=j+1,w[i][j+1]=10;
				else w[i][j+1]=s[j]-'0',sx[w[i][j+1]]=i,sy[w[i][j+1]]=j+1;	
			}
		}
	for(int i=0;i<k;i++) {
		memset(v,0,sizeof(v));
		bfs(sx[i],sy[i],sx[i+1],sy[i+1]);
	}
	cout<<ans<<endl;
	return 0;
}

POJ 3669则带给我另外两点启发:
1、根据题目条件的性质,要 预处理待搜索的图; 在此题中则是要记录该地点被摧毁的最早时间点.
2、充分 理解BFS最短路的原理 ;此题中,即扩展到一个合法点后实际上如果要继续走,则该点一定不会再被走过,因此要更新时间点.

代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

const int N=310;

struct node{
	int x,y,t;
};

int w[N][N];
int m;
int dx[5]={0,0,1,0,-1},dy[5]={0,1,0,-1,0};
node n,t;
int bfs(){
	
	if(w[0][0]==-1) return 0;
	if(w[0][0]==0) return -1;
	
	queue<node> q;
	node tmp;
	tmp.x=0,tmp.y=0,tmp.t=0;
	q.push(tmp);
	
	while(!q.empty()){
		t=q.front();
		q.pop();
		
		for(int i=1;i<=4;i++){
			n.x=t.x+dx[i],n.y=t.y+dy[i];
			if(n.x<0 || n.y<0) continue;
			n.t=t.t+1;
			if(w[n.x][n.y]<=n.t && w[n.x][n.y]!=-1) continue;
			if(w[n.x][n.y]==-1) return n.t;
			w[n.x][n.y]=n.t;
			q.push(n);
		}
		
	}
	return -1;
	
}

int main(){
	scanf("%d",&m);
	memset(w,-1,sizeof(w));
	for(int i=1;i<=m;i++){
		int x,y,t;
		scanf("%d%d%d",&x,&y,&t);
		for(int j=0;j<=4;j++){
			int nx=x+dx[j],ny=y+dy[j];
			if(nx<0 || ny<0 || nx>300 || ny>300) continue;
			if(w[nx][ny]!=-1) w[nx][ny]=min(w[nx][ny],t);
			else w[nx][ny]=t;
		}
	}
	cout<<bfs()<<endl;
	return 0;
    }
二、DFS
1、复健用题

POJ 1979, AOJ 0118, AOJ 0033, AOJ 3009,POJ 3050, POJ 3187,POJ 2718和ACwing上DFS连通性和搜索顺序两类模型的题目.

2、总结

a) POJ 1979, AOJ 0118 两题实际上就是上述提到的 FloodFill 模型,即找连通块,一般这个模型用DFS更好.

POJ 1979:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N=25;

char g[N][N];
int n,m;
int sx,sy;
int ans,v[N][N];
int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};

void dfs(int x,int y){
    if(v[x][y]==1) return ;
    v[x][y]=1;
    ans++;
    for(int i=0;i<4;i++){
        int nx=x+dx[i],ny=y+dy[i];
        if(nx<0 || nx>=n || ny<0 || ny>=m) continue;
        if(g[nx][ny]=='#') continue;
        dfs(nx,ny);
    }
    return ;
}

int main(){

    while(1){
        cin>>m>>n;
        ans=0;
        memset(v,0,sizeof(v));

        if(m==0 || n==0 ) break;
        for(int i=0;i<n;i++)
            for(int j=0;j<m;j++){
                cin>>g[i][j];
                if(g[i][j]=='@')
                sx=i,sy=j;
            }

        dfs(sx,sy);

        cout<<ans<<endl;

    }

    return 0;
} 
AOJ 0118:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N=110;
int w[N][N];
int n,m,ans;
int v[N][N];
int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};

void dfs(int x,int y,int k){
	v[x][y]=1;
	for(int i=0;i<=3;i++){
		int nx=x+dx[i],ny=y+dy[i];
		if(v[nx][ny] || nx<1 || ny<1 || nx>n || ny>m || w[nx][ny]!=k) continue;
		dfs(nx,ny,k);
	}
} 

int main(){
	
	while(1){
		cin>>n>>m;
		if(n==0 && m==0) break;
		
		memset(v,0,sizeof(v));
		memset(w,0,sizeof(w));
		ans=0;
		
		for(int i=1;i<=n;i++){
				char s[N];
				cin>>s;
				for(int j=0;j<strlen(s);j++){
					if(s[j]=='@') w[i][j+1]=1;
					else if(s[j]=='#') w[i][j+1]=2;
					else w[i][j+1]=3;
					//cout<<w[i][j+1];
				}
				//cout<<endl;
			}
			
		for(int i=1;i<=n;i++){
				for(int j=1;j<=m;j++){
				//cout<<w[i][j];
				if(v[i][j]) continue;
				ans++;
				dfs(i,j,w[i][j]);
			}
			//cout<<endl;
		}
		
		cout<<ans<<endl;
		}
	
	return 0;
}

b) POJ 3009 这道题则是用DFS走迷宫的类型题;这道题让我学到的东西是灵活的编写扩展规则.

代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N=25;

int n,m,w[N][N],ans;
int tx,ty,sx,sy;
int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};

void dfs(int x,int y,int step){
	
	if(x==tx && y==ty){
		ans=min(step,ans);
		return ;
	}
	if(step==10 || step>=ans) return ;
	
	for(int i=0;i<=3;i++){
		int nx=x+dx[i],ny=y+dy[i];
		
		while(nx<=n && nx>=1 && ny<=m && ny>=1 && w[nx][ny]!=1){
			if(nx==tx && ny==ty){
				step++;
				ans=min(step,ans);
				return ;
			}
			nx+=dx[i];
			ny+=dy[i];
		}
		if((nx==x+dx[i] && ny==y+dy[i]) || nx<=0 || ny<=0 || nx>n || ny>m) continue;
		w[nx][ny]=0;
		step++;
		dfs(nx-dx[i],ny-dy[i],step);
		step--;
		w[nx][ny]=1;
	}
}

int main(){
	while(1){
	cin>>m>>n;
	if(n==0 && m==0) break;
	memset(w,0,sizeof(w));
	ans=11;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++){
			scanf("%d",&w[i][j]);
			if(w[i][j]==2) sx=i,sy=j;
			if(w[i][j]==3) tx=i,ty=j;
		}
	dfs(sx,sy,0);
	if(ans==11) cout<<-1<<endl;
	else cout<<ans<<endl;
	}
	
	return 0;
}

相反的,POJ 3050是一道很暴力的题目,可以作为DFS的模板来看

POJ 3050:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <set>

using namespace std;

const int N=6;

int w[N][N];
set<int>num;
int dx[4]={0,1,-1,0},dy[4]={1,0,0,-1};
int cnt;

void dfs(int x,int y,int i,int s){
 	if(i==6){
		num.insert(s);
		//cout<<s<<endl;
		return ;
	}
	for(int j=0;j<=3;j++){
		int nx=x+dx[j],ny=y+dy[j];
		if(nx<0 || ny<0 || nx>4 || ny>4) continue;
		i++;
		dfs(nx,ny,i,s*10+w[nx][ny]);
		i--;
	}
}

int main(){
	for(int i=0;i<=4;i++)	
		for(int j=0;j<=4;j++)
			cin>>w[i][j];
	for(int i=0;i<=4;i++)
		for(int j=0;j<=4;j++)
			dfs(i,j,1,w[i][j]);
	cout<<num.size()<<endl;
	return 0;
}

c) POJ 3187,POJ 2718这两道题都是用 DFS 来处理排列问题,但实际上都可以用next_permutation函数来解决

POJ 3187:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N=15;

int a[N];
int sum,n;
int w[N][N];

int main(){
	w[1][1]=1;
	w[2][1]=1,w[2][2]=1;
	w[3][1]=1,w[3][2]=2,w[3][3]=1;
	for(int i=4;i<=10;i++){
		w[i][1]=w[i-1][1];
		for(int j=2;j<i;j++)
			w[i][j]=w[i-1][j-1]+w[i-1][j];
		w[i][i]=w[i-1][i-1];
	}
	
	cin>>n>>sum;
	for(int i=1;i<=n;i++) a[i]=i;
	do{
		int ts=0;
		for(int i=1;i<=n;i++) ts+=a[i]*w[n][i];
		if(ts==sum) {
			for(int i=1;i<=n;i++) cout<<a[i]<<" ";
			break;
		}
	}while(next_permutation(a+1,a+n+1));
	return 0;
}
POJ 2718:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N=15,INF=0x3f3f3f3f;

int a[N];
int T;


int main(){
	
	cin>>T;
	while(T--){
		char c;
		int cnt=0;
		while(1){
			scanf("%d%c",&a[cnt++],&c);
			if(c=='\n') break;
		}
		if(cnt==2){
			cout<<abs(a[0]-a[1])<<endl;
			continue;
		}
		int ans=INF;
		int le,ri;
		int mid=cnt/2;
		do{
			le=a[0],ri=a[mid];
			if(a[0]==0 || a[mid]==0) continue;
			for(int i=1;i<mid;i++) le=le*10+a[i];
			for(int i=mid+1;i<cnt;i++) ri=ri*10+a[i];
			ans=min(ans,abs(ri-le));
		}while(next_permutation(a,a+cnt));
	cout<<ans<<endl;
	}
	return 0;
}
三、小结

1、DFS,BFS在这个阶段除了小部分题目,大部分实际上可以通用;同时也是很多的算法思想的基础,例如网络流的EK算法等,所以应当予以重视;
2、搜索同时也有更高级的技巧,将会在接下来的复健中写到.
3、通过写这些题目,也复习了一些STL的写法例如map,string,set,queue等.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值