0
点赞
收藏
分享

微信扫一扫

P1949 [NOI2001] 聪明的打字员题解(哈希+剪枝+BFS+大模拟)

勇敢乌龟 2022-04-25 阅读 37
c++

知识点: BFS+字符串hash+模拟

笔记:
第一次发题解,可能比较菜,请体谅QAQ,如果有错的话欢迎指出。
6个变换态分别写在bfs中,用广度优先搜索从第一个光标开始搜起,用map标记多少步数
到达该字符串,找到更新最大值即可,用了这思路,超时超的妈都不认识。(结果正确,18分)

所以这题果断舍弃map(看来以后要少用,两次教训),用一个6维数组标记密码(这里引入一个函数stoi,将字符串转变成数字,头文件为cstring),以空间换时间(六位不会炸),另外写一个函数给六维数组赋个值,然后的话另外弄一个二维数组,第一个空放6维数组,第二个空放下标,以此来标记状态,但依然过不了QAQ

然后的话,改成双向BFS,可以是可以但是依然是有区别的,由于最终状态的游标可以在任意地点,所以的话要把结尾的六个状态塞进去(游标分别在六个地方)。

但依旧TLE,看来只能手算了,把中间那部分自行手算解决。(再省去那部分循环的时间)同时中间可以加个优化,如果当前的数小于或大于目标数中的最小值或最大值,则禁止增加到比其大或比其小。(加上这个优化后不再TLE了,成绩涨到了55分QAQ)

然后找到了几个小细节就AC了。(在程序中标记出来)

AC代码解析如下:

#include<iostream>
#include<map>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
string s;
int best=0,now_cur=0,big=0;
struct pel{
	string st;(这里放当前的字符串)
	int dir;(这里标记是从起始状态还是末尾状态搜起)
	int mark;(这个标记游标所在的位置)
};
bool sus=0;(判断是否已经找到答案)
queue<pel> q;(搜索队列)
string cur,edi;(开始和末尾的字符串)
int maxn=0;
int f[3000000][6],mid[3000000][6],pwd[10][10][10][10][10][10];(f用来记录步数,mid用来判断当前状态是起始的还是最终开始搜起的,而pwd则是用来给当前字符串哈希的)
int get_val(string tems){(用来得到它的哈希值)
	int num[7];
	num[1]=tems[0]-48;
	num[2]=tems[1]-48;
	num[3]=tems[2]-48;
	num[4]=tems[3]-48;
	num[5]=tems[4]-48;
	num[6]=tems[5]-48;
	return pwd[num[1]][num[2]][num[3]][num[4]][num[5]][num[6]];
}
void gi_val(string tems){
	int num[7];
    num[1]=tems[0]-48;
	num[2]=tems[1]-48;
	num[3]=tems[2]-48;
	num[4]=tems[3]-48;
	num[5]=tems[4]-48;
	num[6]=tems[5]-48;
	big++;(用big给予不同的数来哈希)
	pwd[num[1]][num[2]][num[3]][num[4]][num[5]][num[6]]=big;
}
string swap0(string tems,int temm){(根据题意)
	if(temm==0)return tems;
	swap(tems[temm],tems[0]);
	return tems;
}
string swap1(string tems,int temm){ (根据题意)
	if(temm==5)return tems;
	swap(tems[temm],tems[5]);
	return tems;
}
string up(string tems,int temm){
	if(tems[temm]>=maxn+48)return tems;(注意,这里剪枝只能是最大值,不然会WA)
	tems[temm]=char(int(tems[temm])+1);
	return tems;
}
string down(string tems,int temm){
	if(tems[temm]=='0')return tems;
	tems[temm]=char(int(tems[temm])-1);
	return tems;
}
void check() { (判断最糟糕的情况的best,并以此来剪枝)
    int a=cur[0]-48,b=cur[1]-48,c=cur[2]-48,d=cur[3]-48,
	    e=cur[4]-48,f=cur[5]-48;
    int A=edi[0]-48,B=edi[1]-48,C=edi[2]-48,D=edi[3]-48,
	    E=edi[4]-48,F=edi[5]-48;
    best=abs(a-A)+abs(b-B)+abs(c-C)+abs(d-D)+abs(e-E)+abs(f-F)+5;
}
void imp(string nes,int nemk,string nws,int nwmk,int direc){
	if(nes==nws)return ;
	pel temp;
	temp.st=nes,temp.mark=nemk,temp.dir=direc;
	if(get_val(nes)==0)gi_val(nes);(如果这个字符串没有被哈希过给他个哈希值)
	int nex_mar=get_val(nes);(得到哈希值方便当前操作)
	
	if(direc==mid[nex_mar][nemk])return ;
	
	if((direc==1&&mid[nex_mar][nemk]==3)||(direc==3&&mid[nex_mar][nemk]==1)){(当撞头了肯定是最小的距离,直接标记sus=1(因为bfs是逐步延伸扩展开的,先碰到的肯定是最小的))
		best=f[nex_mar][nemk]+f[now_cur][nwmk];
		sus=1;
		return ;
    }
	
	if(!f[nex_mar][nemk]||f[nex_mar][nemk]>f[now_cur][nwmk]+1){
		f[nex_mar][nemk]=f[now_cur][nwmk]+1;
		mid[nex_mar][nemk]=direc;(延续前面的路线)
		q.push(temp);
	}
	
}
int bfs(){
	pel next;int i;
	next.st=cur,next.mark=0;next.dir=1;
	q.push(next),gi_val(cur);
	f[1][0]=0,mid[1][0]=1;
	
	next.st=edi,gi_val(edi),next.dir=3;
	for(i=0;i<=5;i++){(由于有6个游标,所以都需要塞进去)
		next.mark=i;
		f[2][i]=1;(这里必须是1,因为当我们正向搜过去的时候其倒数的步数会加1到最终状态,而双向的要考虑到这一点)
		mid[2][i]=3;
		q.push(next);
	}(前面这些都是双向BFS的准备工作)
	
	int now_mk=0,now_dir;
	string now_st;
	while(!q.empty()){
		now_st=q.front().st;
		now_mk=q.front().mark;
		now_cur=get_val(now_st);
		now_dir=q.front().dir;
        
		q.pop();
		
		if(f[now_cur][now_mk]>best/2)continue;(其中一个剪枝)
		

        剩下的都是大模拟
		next.st=swap0(now_st,now_mk);
		next.mark=now_mk;
		imp(next.st,next.mark,now_st,now_mk,now_dir);
		if(sus)return best;
		
		next.st=swap1(now_st,now_mk);
		next.mark=now_mk;
		imp(next.st,next.mark,now_st,now_mk,now_dir);
	    if(sus)return best;
		
		next.st=up(now_st,now_mk);
		next.mark=now_mk;
		imp(next.st,next.mark,now_st,now_mk,now_dir);
		if(sus)return best;
		
		next.st=down(now_st,now_mk);
		next.mark=now_mk;
		imp(next.st,next.mark,now_st,now_mk,now_dir);
		if(sus)return best;
		
		next.st=now_st;
		next.dir=now_dir;
		if(now_mk>=1){
			next.mark=now_mk-1;
			if(!f[now_cur][next.mark]||f[now_cur][next.mark]>f[now_cur][now_mk]+1){
				f[now_cur][next.mark]=f[now_cur][now_mk]+1;
				mid[now_cur][next.mark]=now_dir;
				q.push(next);
			}
		}
		if(now_mk<=4){
			next.mark=now_mk+1;
			if(!f[now_cur][next.mark]||f[now_cur][next.mark]>f[now_cur][now_mk]+1){
				f[now_cur][next.mark]=f[now_cur][now_mk]+1;
				mid[now_cur][next.mark]=now_dir;
				q.push(next);
			}
		}
		
	}
	return best;
}
int main(){
	memset(mid,2,sizeof(mid));
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);(输出优化)
	
	cin>>cur;
	cin>>edi;
	check();
	int i;
	for(i=0;i<=5;i++)
		maxn=max(maxn,edi[i]-48);
	if(best==5){
		cout<<0;
		return 0;
	}

	cout<<bfs();
}

总结:这题咋一看题面好像很水,其实一堆细节的东西,并且还要加上优化不然铁定会TLE,还要注意MLE,粗略估计题目数据范围,事实证明,STL虽然好用但是在很多时候其会严重拖慢程序运行速度,使用需谨慎。

举报

相关推荐

0 条评论