搜索与搜索剪枝3

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网

八数码


You will receive a description of a configuration of the 8 puzzle. The description is just a list of the tiles in their initial positions, with the rows listed from top to bottom, and the tiles listed from left to right within a row, where the tiles are represented by numbers 1 to 8, plus 'x'.

You will print to standard output either the word ``unsolvable'', if the puzzle has no solution, or a string consisting entirely of the letters 'r', 'l', 'u' and 'd' that describes a series of moves that produce a solution. The string should include no spaces and start at the beginning of the line.

此题堪称深搜广搜的完美结合,外加数据处理能力的考察,不可多得,值得多做(还不是因为你想不出来)!QAQ

核心思想:可以通过广搜进行下一步状态的确定,并且要有对重复的判断以免多走,同时由于广搜的性质决定了一定是最小步数;直到找到预期的答案停止查找,后通过之前记录的信息深搜递归一步步返回进行答案的输出。我们将数字串直接转化为字符串(若当成九位数字会是longlong类型,比较麻烦),并用map映射成一个数字,用以标记是否记录过某字符串,并提供找到答案时的标号用以回溯;q记录访问编号,问为啥不直接记录探索过的string?因为需要广搜起点的标号用以回溯,所以就用q存标号,这样只要再加一个数字映射为字符串的数组就行了;a的string类型字符串就是用做这个。

细节问题:回溯时的初始标号当然是找到答案时的数字了;好好想想操作到底对应着哪个方向QAQ

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#include<map>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=1030,inf=0x3f3f3f3f,N=363000;//状态总数:9的全排列 
string s="",t="12345678x";
int last[N];//用于回溯原状态
char from[N];//从什么方向来的 
map<string,int>mp;
string a[N];//a[i]表示第i个状态的字符串 
int cnt=0;//mp中有多少个元素 
queue<int>q;
const int dir[4][2]={0,1,0,-1,-1,0,1,0};
const char d[4]={'r','l','u','d'};
bool bfs(string s,string t){
	mp[s]=1;//第一个元素
	cnt=1;
	a[1]=s;//作用:开一个双向的映射,把字符串映射成数字,反之也可 
	q.push(1); 
	while(!q.empty()){
		int tmp=q.front();
		string cur=a[tmp];//将当前字符串取出,找x的位置
//		cout<<cur<<endl;
		q.pop(); 
		int pos=cur.find('x');//获取x的位置
		int x=pos/3,y=pos%3;
		for(int i=0;i<4;++i){
			int dx=x+dir[i][0];
			int dy=y+dir[i][1];
			if(dx<0||dy<0||dx>=3||dy>=3) continue;//越界 
			string nxt=cur;//不能改动原来的字符串 
			swap(nxt[pos],nxt[dx*3+dy]);//将x的位置交换
			if(mp.find(nxt)!=mp.end()) continue;//若发现以前走过,继续搜 
			mp[nxt]=++cnt;//下一个状态 
			a[cnt]=nxt;//存本状态的字符串 
			last[cnt]=tmp;//上一个元素的位置 
			from[cnt]=d[i];// 从哪个方向来
			q.push(cnt);
			if(nxt==t) return 1; 
		} 
	} 
	return 0;
}
void dfs(int x){
	if(last[x]!=0){
		dfs(last[x]);
		cout<<from[x];
	}
}
int main(){
	for(int i=1;i<=9;++i){
		char a;
		cin>>a;
		s+=a;
	}
	if(bfs(s,t)==0) cout<<"unsolvable";
	else dfs(mp[t]);
	return 0;
}

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
 

[NOIP2002]字串变换

已知有两个字串 A, B及一组字串变换的规则(至多6个规则):
A1 -> B1
A2 -> B2
规则的含义为:在A中的子串 A1可以变换为 B1、A2可以变换为 B2 …。
例如:A='abcd' B='xyz'
变换规则为:
‘abc’->‘xu’ ‘ud’->‘y’ ‘y’->‘yz’
则此时,A 可以经过一系列的变换变为 B,其变换的过程为:
‘abcd’->‘xud’->‘xy’->‘xyz’
共进行了三次变换,使得A变换为B。

此题最主要的卡壳点在于,如果让原字符串与答案字符串双向奔赴,那么就可以将问题规模减少一半,从而减少不必要的运行时间与占用内存;

细节问题:1、不要死板回溯ans,可以开一个dis数组存放步数;2、要是数组空间给得比较紧,可以通过使用vector,用多少拿多少;3、while(cin>>a[n]>>b[n]) n++ 读到返回1,否则返回0 ;4、scanf("%d%d%d") 读够3个数返回3,没读够返回EOF 。

string用法整理:

判断是否字符串中没有指定子串:str.find(substr)==string::npos

指定位置之后子串位置:str.find(substr, pos)

替换字符串中指定子串:str.replace(pos, len, targetstr)

擦除指定部位子串:str.erase(pos, substr.length)

指定部位插入子串,原位置字符后移:str.insert(pos, substr)

memset使用细节:memset(array,0,sizeof(array)) 清空array时,若array是作为指针传入则会无法获取sizeof(array),所以不能在函数中对传入的指针直接使用memset。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#include<map>
#include<vector>
using namespace std;
typedef long long ll;
const int N=1000000,inf=0x3f3f3f3f;
string A,B,a[8],b[8];
int disa[N],disb[N];
vector<string>st;
map<string,int>mp;
queue<int>q;
int n=1,cnt=-1;
bool bfs(string s,int dis[],string a[],string b[]){
	while(!q.empty()) q.pop();
	mp[s]=++cnt;//与st一致,从0开始算下标 
	st.push_back(s); 
	dis[cnt]=0;
	q.push(cnt);
	while(!q.empty()){
		int tmp=q.front(); q.pop();
		if(dis[tmp]==5) return 0;
		//由于是对半搜,所以不能超过5(另一边就交给下一次吧,如果真能合并 就一定能 
		string cur=st[tmp];
//		cout<<cur<<endl;
		int l=cur.length();
		for(int i=1;i<n;++i){ 
			for(int j=0;j<l;){
				string nxt=cur;
				int pos=cur.find(a[i],j);
				if(pos==string::npos) break;
				j=pos+1;
				nxt.erase(pos,a[i].length());
				nxt.insert(pos,b[i]);
				if(mp.find(nxt)==mp.end()){
					mp[nxt]=++cnt;
					st.push_back(nxt);
					dis[cnt]=dis[tmp]+1;
					q.push(cnt);
					if(nxt==B) return 1;//<=对第二次广搜没用 
				}else if(dis[mp[nxt]]==-1){//在第二次广搜中出现,意味着a走过了,且是走到末路的情况 
					dis[mp[nxt]]=dis[tmp]+1;//这时b在之前的基础上加步数,实际上这相当于已经有一组解了 
					q.push(cnt);//实际上,disa[tmp]=disa[mp[nxt]],即到达这个字符串的步数 
				}//这里push的原因是,cnt可以帮助我们接下来查找下一个字符串 
			}
		}
	}
	return 0;
}
int main(){
	cin>>A>>B;
	while(cin>>a[n]>>b[n]) n++;
	memset(disa,-1,sizeof(disa));
	memset(disb,-1,sizeof(disb));
	if(bfs(A,disa,a,b)) {
		cout<<disa[mp[B]];
		return 0;
	}
	bfs(B,disb,b,a);
	int ans=1000;
	for(int i=0;i<=cnt;++i){
		if(disa[i]!=-1&&disb[i]!=-1){
			ans=min(ans,disa[i]+disb[i]);
		}
	}
	if(ans>10) cout<<"NO ANSWER!"; 
	else cout<<ans;
	return 0;
}

又是被自己菜枯的一天QAQ 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值