问题 C: 【宽搜入门】8数码难题
思路为:将每种棋盘对应成一个整数,整数存进map
表示已经出现过这种状态,对于每种状态,转化成3x3的矩阵,判断是否有下一种状态存进queue
,,对于每种状态存取到达这步的步数;BFS宽搜。
提升 这道题做到这我就完了,但是其实它有很多优化问题我没想到,甚至是AI大作业;无解情况存在,但题目不涉及,也考虑是题目比较简单;对于阶乘计算9!;**DBFS**双向深搜,还不理解是在干啥
#include<bits/stdc++.h>
// #include<queue>
// #include<map>
// #include<iostream>
using namespace std;
#define pa pair<int,int>
int mat[3][3],des[3][3];
int srcNum,desNum;
int ans;
map<int,int> reach;
map<int,int> step;
int d_x[4]={-1,1,0,0};
int d_y[4]={0,0,-1,1};
int change(int a[3][3]){
int rnt=0;
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
rnt=rnt*10+a[i][j];
}
}
return rnt;
}
void num2mat(int a){
for(int i=2;i>=0;i--){
for(int j=2;j>=0;j--){
mat[i][j]=a%10;
a/=10;
}
}
return;
}
bool same(int a[3][3],int b[3][3]){//n
bool flag=true;
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
if(a[i][j]!=b[i][j]){
flag=false;
break;
}
}
}
return flag;
}
void dfs(){
queue<int>q;
q.push(srcNum);
reach[srcNum]=1;
step[srcNum]=1;
while (!q.empty())
{
int now=q.front();
// num2mat(now);
if(now==desNum){
ans=step[now];
return;
}
q.pop();
num2mat(now);
int x,y;
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
if(mat[i][j]==0){
x=i,y=j;
break;
}
}
}
int n_x,n_y;
for(int i=0;i<4;i++){
n_x=x+d_x[i];
n_y=y+d_y[i];
if(n_x>=0&&n_x<3&&n_y>=0&&n_y<3){
mat[x][y]=mat[n_x][n_y];
mat[n_x][n_y]=0;
int after=change(mat);
if(reach[after]!=1){
q.push(after);
reach[after]=1;
step[after]=step[now]+1;
}
mat[n_x][n_y]=mat[x][y];//恢复原矩阵
mat[x][y]=0;
}
}
}
return;
}
int main(){
// freopen("a.in","r",stdin);
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
cin>>mat[i][j];
// cout<<mat[i][j]<<"-----";
}
}
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
cin>>des[i][j];
}
}
srcNum=change(mat);
desNum=change(des);
dfs();
cout<<ans<<'\n';
}
小结
- 状态转移:将每个棋盘布局看做一种状态,即为bfs搜索问题
- 状态对应数值的互相转化
- 细节:矩阵的频繁操作;二维转一维与一维转二维的对应
问题 D: 【宽搜入门】魔板
在成功地发明了魔方之后,鲁比克先生发明了它的二维版本,称作魔板。这是一张有8个大小相同的格子的魔板:
1 2 3 4
8 7 6 5
我们知道魔板的每一个方格都有一种颜色。这8种颜色用前8个正整数来表示。可以用颜色的序列来表示一种魔板状态,规定从魔板的左上角开始,沿顺时针方向依次取出整数,构成一个颜色序列。对于上图的魔板状态,我们用序列(1,2,3,4,5,6,7,8)来表示。这是基本状态。
这里提供三种基本操作,分别用大写字母“A”,“B”,“C”来表示(可以通过这些操作改变魔板的状态):
“A”:交换上下两行;
“B”:将最右边的一列插入最左边;
“C”:魔板中央四格作顺时针旋转。
下面是对基本状态进行操作的示范:
#include<bits/stdc++.h>
// #include<vector>
// #include<queue>
// #include<map>
// #include<iostream>
// #include<string.h>
using namespace std;
// #define pa pair<int,int>
char mat[4][4];
string des;
map<string,string> route;
map<string,int> vis;
map<int,char>op={{0,'A'},{1,'B'},{2,'C'}};
void s2mat(string s){
int cnt=0;
for(int i=0;i<4;i++,cnt++) mat[0][i]=s[cnt];
for(int i=3;i>=0;i--,cnt++) mat[1][i]=s[cnt];
return;
}
string change(int mode){
if(mode==0){
char t[4];
for(int i=0;i<4;i++) t[i]=mat[0][i],mat[0][i]=mat[1][i],mat[1][i]=t[i];
}
else if (mode==1)
{
char t[2];
t[0]=mat[0][3],t[1]=mat[1][3];
for(int i=3;i>=1;i--){
mat[0][i]=mat[0][i-1];
mat[1][i]=mat[1][i-1];
}
mat[0][0]=t[0];
mat[1][0]=t[1];
}
else{
char a[4];
a[0]=mat[0][1],a[1]=mat[0][2],a[2]=mat[1][1],a[3]=mat[1][2];
mat[0][1]=a[2],mat[0][2]=a[0],mat[1][1]=a[3],mat[1][2]=a[1];
}
string ans="";
for(int i=0;i<4;i++) ans+=mat[0][i];
for(int i=3;i>=0;i--) ans+=mat[1][i];
return ans;
}
void bfs(string s){
queue<string> q;
q.push(s);
while (q.size())
{
string now=q.front();
vis[now]=1;
q.pop();
if(now==des){
cout<<route[now].size()<<"\n";
cout<<route[now]<<"\n";
return;
}
else{
s2mat(now);
string after;
for(int i=0;i<3;i++){
s2mat(now);
after=change(i);
if(vis[after]==0){
vis[after]=1;
route[after]=route[now]+op[i];
q.push(after);
}
}
}
}
return;
}
int main(){
string src;
while (getline(cin,src)){
route.clear();
vis.clear();
char c;
for(int i=0;i<15;i+=2){
c=src[i];
des+=c;
// cin>>c;
}
// cout<<des<<"===\n";
bfs("12345678");
}
return 0;
}
问题 E: 【宽搜入门】巧妙取量
题目描述】
有三个容器,容量分别为 a,b,c(a> b > c ),一开始a装满油,现在问是否只靠abc三个容器量出k升油。如果能就输出“yes”,并且说明最少倒几次,否则输出“no”。例如:10升油在10升的容器中,另有两个7升和3升的空容器,要求用这三个容器倒油,使得最后在abc三个容器中有一个刚好存有5升油,问最少的倒油次数是多少?(每次倒油,A容器倒到B容器,或者A内的油倒完,或者B容器倒满。
思路对于每个状态,都可以从一个桶倒向另一个桶,产生新的状态,宽搜记录达到的状态和步数,其中一个倒向另一个的逻辑是:设AB两个容器,从A倒向B,里面分别含有v1,v2,最多为v1max,v2max,B还可以容纳vfree=v2max-v2;v1>vfree,B倒满;否则,v1全部导入B;
记录所有的状态和步数用结构体,注意不能直接将结构体作为map
的key
,出现的问题是结构体里面含有数组,不能唯一作为key,使用数组原因是遍历时候方便遍历,不用谢很多的逻辑判断;(这里的结构体即使重定义“<”"=="也不行,自己可以试试有一定的黑盒性很难判断,不过最终证明这种写法会导致内存溢出,占内存太大);将每个状态只存一遍在vector中(push是深复制),然后使用索引在queue和vis中操作。
其他思路启发由于数据范围小,可以直接用三维数组标记是否到达过该状态
#include<bits/stdc++.h>
using namespace std;
// #define pa pair<int,int>
struct node{
int a[3]={0};
int step=0;
bool operator <(const node &n) const{
return step<n.step;
}
bool operator ==(const node &n) const{
bool flag=true;
for(int i=0;i<3;i++){
if(a[i]!=n.a[i]){
flag= false;
break;
}
}
return flag;
}
// void operator = (const node& n)const{
// for(int i=0;i<3;i++){
// a[i]=n.a[i];
// }
// step=n.step;
// }
}node0;
vector<node>v;
int k;
int cnt=0;
int vmax[3];
map<int,int>vis;
queue<int> q;
node temp;
bool search(node& nn){
for(auto item:v){
bool flag=true;
for(int i=0;i<3;i++){
if(item.a[i]!=nn.a[i]){
flag= false;
break;
}
}
if(flag) return true;
}
return false;
}
void bfs(int n){
q.push(n);
while (q.size()){
int now=q.front();
temp=v[now];//队列出入的大量操作
vis[now]=1;//消失
if(temp.a[0]==k||temp.a[1]==k||temp.a[2]==k){
cout<<"yes\n"<<temp.step<<"\n";
return;
}
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
if(j!=i){
for(int i=0;i<3;i++) node0.a[i]=temp.a[i];
int vfree=vmax[j]-temp.a[j];
if(temp.a[i]>=vfree){
node0.a[i]=node0.a[i]-vfree;
node0.a[j]=vmax[j];
}
else{
node0.a[j]+=temp.a[i];
node0.a[i]=0;
}
node0.step=temp.step+1;
if(search(node0)== false){
cnt++;
v.push_back(node0);
vis[cnt]=1;
q.push(cnt);
}
}
}
}
q.pop();
}
cout<<"no\n";
return;
}
int main(){
// freopen("a.in","r",stdin);
while (cin>>vmax[0]>>vmax[1]>>vmax[2]>>k){
while (q.size()){//bug 最后直接输出yes 会导致队列内还有元素
q.pop();
}
vis.clear();
v.clear();
cnt=0;
node0.a[0]=vmax[0];
node0.a[1]=0;
node0.a[2]=0;
node0.step=0;
v.push_back(node0);
bfs(0);
}
return 0;
}
小结
- map的key使用结构体要重定义<,但是对于结构体内还有数组的不管用,因此判断存在某个还是用自己写search判断
- queue最后还会剩元素