去年就想做这个题,一直拖到今年。照着lyyy的方法做。
5*5的棋盘,3种状态,3^25次方,longlong 可以存储。
一、用set判重状态。先写个宽搜试试,结果当然20分。
//修改自题解,笨笨的bfs,set判重
#include<iostream>
#include<cstdio>
#include<set>
#include<queue>
using namespace std;
typedef long long ll;
char c[6][6];
int dx[11]= {0,0,1,-1,1,-1,2,-2,2,-2},dy[11]= {0,0,2,-2,-2,2,1,-1,-1,1} ; //来回为相邻的两个方向
char mb[6][6]= {'0','0','0','0','0','0','0','1','1','1','1','1','0','0','1','1','1','1','0','0','0','2','1','1','0','0','0','0','0','1','0','0','0','0','0','0',};
ll goal;
struct node {
char c[6][6];
int x,y,step,cnt;//step已经走的步数,cnt还需要最少再走的步数,后面a*用的。
bool operator <(node a)const {
return cnt+step>a.step+a.cnt;
}
} head;
bool judge(int x,int y) {
if(x<1||x>5)return false;
if(y<1||y>5)return false;
return true;
}
ll hs3(char d[][6]) {
ll ans=0;
for(int i=1; i<=5; i++)
for(int j=1; j<=5; j++)
ans=ans*3ll+d[i][j]-'0';
return ans;
}
void swap(char &c1,char &c2) {
char c3=c1;c1=c2;c2=c3;
}
queue<node> pq;
int bfs(node head) {
if(head.cnt>15)return -1;
set<ll>st;
while(!pq.empty())pq.pop();
pq.push(head);
st.insert(hs3(head.c));
node p,tp;
while(!pq.empty()) {
p=pq.front();
pq.pop();
if(hs3(p.c)==goal)return p.step;
for(int i=2; i<10; i++) {//扩展队首元素
int tx=p.x+dx[i],ty=p.y+dy[i];
if(!judge(tx,ty))continue;//越界不要,超过15不要
tp=p;
swap(tp.c[tx][ty],tp.c[p.x][p.y]);
tp.step=p.step+1;
if(tp.step>15)continue;
tp.x=tx,tp.y=ty;
ll tmp= hs3(tp.c);
if(!st.count(tmp)){
pq.push(tp);
st.insert(tmp);
}
}
}
return -1;
}
int main() {
int t,k;
goal=hs3(mb); //mb是目标状态
scanf("%d",&t);
while(t--) {
k=0;
int px,py;
for(int i=1; i<=5; i++)
for(int j=1; j<=5; j++)
cin>>c[i][j];
for(int i=1; i<=5; i++)//k是有多少骑士不在目的地
for(int j=1; j<=5; j++) {
if(c[i][j]=='*')px=i,py=j,c[i][j]='2';
if(c[i][j]!=mb[i][j]&&c[i][j]!='2')k++;
head.c[i][j]=c[i][j];
}
head.x=px,head.y=py;
head.step=0,head.cnt=k;
hs3(head.c);
node tp=head;
swap(tp.c[px][py],tp.c[px+1][py+2]);
hs3(tp.c);
cout<<bfs(head)<<endl;
}
}
二、双向宽搜。因为这个题目多组数据,但目标状态是一样,因此先从目标状态扩展一次,用map记录下到达一半(大半)状态的步数。然后每次从给定状态扩展,从所有能扩展出的状态种取最小值。还可以用从头尾同时扩展的双向搜索,那么找到的第一个状态就是。
//笨笨的bfs,set判重,map记录第一次宽搜的步数。
#include<iostream>
#include<cstdio>
#include<set>
#include<queue>
#include<map>
#include<string.h>
using namespace std;
typedef long long ll;
char c[6][6];
int dx[11]= {0,0,1,-1,1,-1,2,-2,2,-2},dy[11]= {0,0,2,-2,-2,2,1,-1,-1,1} ; //来回为相邻的两个方向
char mb[6][6]= {'0','0','0','0','0','0','0','1','1','1','1','1','0','0','1','1','1','1','0','0','0','2','1','1','0','0','0','0','0','1','0','0','0','0','0','0',};
ll goal;
struct node {
char c[6][6];
int x,y,step,cnt;
ll hs;
bool operator <(node a)const {
return cnt+step>a.step+a.cnt;
}
} head;
bool judge(int x,int y) {
if(x<1||x>5||y<1||y>5)return false;
return true;
}
ll hs3(char d[][6]) {
ll ans=0;
for(int i=1; i<=5; i++)
for(int j=1; j<=5; j++)
ans=ans*+d[i][j]-'0';
// cout<<"hs3<<"<<ans<<endl;
return ans;
}
void swap(char &c1,char &c2) {
char c3=c1;c1=c2;c2=c3;
}
queue<node> pq;
map<ll,int>mp;
void bfs0(node head) {
while(!pq.empty())pq.pop();
pq.push(head);
mp[head.hs]=head.step;
node p,tp;
while(!pq.empty()) {
p=pq.front();
pq.pop();
if(p.step>8)break;
for(int i=2; i<10; i++) {
int tx=p.x+dx[i],ty=p.y+dy[i];
if(!judge(tx,ty))continue;//越界不要,超过15不要
tp=p;
swap(tp.c[tx][ty],tp.c[p.x][p.y]);
tp.step=p.step+1;
tp.hs=hs3(tp.c);
tp.x=tx,tp.y=ty;
if(!mp.count(tp.hs)){
pq.push(tp);
mp[tp.hs]=tp.step;
}
}
}
return;
}
int bfs(node head) {
int ans=20;
set<ll>st;
while(!pq.empty())pq.pop();
pq.push(head);
st.insert(head.hs);
node p,tp;
while(!pq.empty()) {
p=pq.front();
pq.pop();
if(mp.count(p.hs))ans=min(ans,p.step+mp[p.hs]);
if(p.step>7)break;
for(int i=2; i<10; i++) {
int tx=p.x+dx[i],ty=p.y+dy[i];
if(!judge(tx,ty))continue;//越界不要,超过15不要
tp=p;
swap(tp.c[tx][ty],tp.c[p.x][p.y]);
tp.step=p.step+1;
tp.x=tx,tp.y=ty;
ll tmp= hs3(tp.c);
if(!st.count(tmp)){
pq.push(tp);
st.insert(tmp);
}
}
}
if(ans>15)return -1;
else return ans;
}
int main() {
int t,k;
memcpy(head.c,mb,36);
head.hs=hs3(head.c);
head.x=head.y=3;
head.step=0;
bfs0(head);
scanf("%d",&t);
while(t--) {
k=0;
int px,py;
for(int i=1; i<=5; i++)
for(int j=1; j<=5; j++)
cin>>c[i][j];
for(int i=1; i<=5; i++)//k是有多少骑士不在目的地
for(int j=1; j<=5; j++) {
if(c[i][j]=='*')px=i,py=j,c[i][j]='2';
if(c[i][j]!=mb[i][j]&&c[i][j]!='2')k++;
head.c[i][j]=c[i][j];
}
head.x=px,head.y=py;
head.step=0,head.cnt=k;
head.hs=hs3(head.c);
cout<<bfs(head)<<endl;
}
}
三、Astar算法
用这个题目好像理解了(1)“估价函数要比最优结果还要优”的原则。和宽搜的不同之处在于,当前状态扩展出去的状态都入优先队列,(2)从优先队列出来时候,这个状态才做标记,因为处理过的才是估价后最优的,随着确定状态的不断增多,估价函数只会将最接近目标状态的值放在前面,这样(3)第一个找到的状态就是目标状态。
//修改自题解
#include<iostream>
#include<cstdio>
#include<set>
#include<queue>
using namespace std;
typedef long long ll;
char c[6][6];
int dx[11]= {0,0,1,-1,1,-1,2,-2,2,-2},dy[11]= {0,0,2,-2,-2,2,1,-1,-1,1} ; //来回为相邻的两个方向
char mb[6][6]= {'0','0','0','0','0','0','0','1','1','1','1','1','0','0','1','1','1','1','0','0','0','2','1','1','0','0','0','0','0','1','0','0','0','0','0','0',};
ll goal;
struct node {
char c[6][6];
int x,y,step,cnt;
ll hs;
bool operator <(node a)const {
return (cnt+step)>(a.step+a.cnt);
}
} head;
bool judge(int x,int y) {
if(x<1||x>5||y<1||y>5)return false;
return true;
}
ll hs3(char d[][6]) {
ll ans=0;
for(int i=1; i<=5; i++)
for(int j=1; j<=5; j++)
ans=ans*3ll+d[i][j]-'0';
return ans;
}
int get_cnt(char d[][6]) {
int ans=0;
for(int i=1; i<=5; i++)
for(int j=1; j<=5; j++)
if(d[i][j]!='2'&&d[i][j]!=mb[i][j])ans++;
return ans;
}
void print(char c[][6]) { //调试用,可无视
for(int i=1; i<=5; i++) {
for(int j=1; j<=5; j++)cout<<c[i][j];
cout<<endl;
}
cout<<endl;
}
void swap(char &c1,char &c2) {
char c3=c1;
c1=c2;
c2=c3;
}
priority_queue<node> pq;
set<ll>st;
int Abfs(node head) {
if(head.cnt>15)return -1;
st.clear();
while(!pq.empty())pq.pop();
pq.push(head);
node p,tp;
while(!pq.empty()) {
p=pq.top();
while(!pq.empty()&&st.count(p.hs)){
pq.pop();
if(!pq.empty())p=pq.top();
}
if(p.hs==goal)return p.step;
if(!pq.empty())pq.pop();
st.insert(p.hs);
for(int i=2; i<10; i++) {
tp=p;
tp.x=p.x+dx[i],tp.y=p.y+dy[i];
if(!judge(tp.x,tp.y))continue;//越界不要,超过15不要
tp.cnt=p.cnt-(p.c[tp.x][tp.y]!=mb[tp.x][tp.y]);
tp.cnt+=(p.c[tp.x][tp.y]!=mb[p.x][p.y]);
swap(tp.c[tp.x][tp.y],tp.c[p.x][p.y]);
tp.step=p.step+1;
if(tp.step+tp.cnt>15)continue;
tp.hs=hs3(tp.c);
pq.push(tp);
}
}
return -1;
}
int main() {
int t,k;
goal=hs3(mb);
scanf("%d",&t);
while(t--) {
k=0;
int px,py;
for(int i=1; i<=5; i++)
for(int j=1; j<=5; j++)
cin>>c[i][j];
for(int i=1; i<=5; i++)//k是有多少骑士不在目的地
for(int j=1; j<=5; j++) {
if(c[i][j]=='*')px=i,py=j,c[i][j]='2';
if(c[i][j]!=mb[i][j]&&c[i][j]!='2')k++;
head.c[i][j]=c[i][j];
}
head.x=px,head.y=py;
head.step=0,head.cnt=k;
head.hs=hs3(head.c);
cout<<Abfs(head)<<endl;
}
}
四、IDEA*
IDE就是按层展开的dfs,增加一个搜索层数的限制条件,在dfs内用A*就是扩展节点是加了带估价函数的剪枝,如果当前状态用的步数+还有走的最优步数也无法到达目标,那就不要走下去了。代码量比较小。
//修改自题解
#include<iostream>
#include<cstdio>
using namespace std;
char c[6][6];
int dx[11]= {0,0,1,-1,1,-1,2,-2,2,-2},dy[11]= {0,0,2,-2,-2,2,1,-1,-1,1} ; //来回为相邻的两个方向
string mb[6];
bool judge(int x,int y) {
if(x<1||x>5)return false;
if(y<1||y>5)return false;
return true;
}
void print() { //调试用,可无视
for(int i=1; i<=5; i++) {
for(int j=1; j<=5; j++)cout<<c[i][j];
cout<<endl;
}
cout<<endl;
}
void swap(char &c1,char &c2) {
char c3=c1;
c1=c2;
c2=c3;
}
int i,j,k,m,n,ans;
void trys(int step,int cnt,int px,int py,int last) { //x是当前搜索层数,y是剩余多少骑士未到达,pxpy是*的位置,last是上层搜索操作
if(step+cnt>j)return;//剪枝
if(ans>0)return;//出解后不必继续搜索
if(cnt==0) {
ans=step;//print();
return;
}
for(int i=2; i<10; i++) {
if(judge(px+dx[i],py+dy[i])&&(last^i)!=1) {
int now=cnt+(c[px+dx[i]][py+dy[i]]==mb[px+dx[i]][py+dy[i]]);//找骑士
now-=(c[px+dx[i]][py+dy[i]]==mb[px][py]);//感筛省?
swap(c[px+dx[i]][py+dy[i]],c[px][py]);
trys(step+1,now,px+dx[i],py+dy[i],i);
swap(c[px+dx[i]][py+dy[i]],c[px][py]);
}
}
}
int main() {
mb[1]="011111";
mb[2]="001111";
mb[3]="000*11";
mb[4]="000001";
mb[5]="000000";
//mb是目标状态
int t;
scanf("%d",&t);
while(t--) {
k=0;
for(i=1; i<=5; i++)
for(j=1; j<=5; j++)
cin>>c[i][j];
for(i=1; i<=5; i++)
for(j=1; j<=5; j++)
if(c[i][j]!=mb[i][j]&&c[i][j]!='*')k++;//k是有多少骑士不在目的地
int px,py;
for(i=1; i<=5; i++)
for(j=1; j<=5; j++)
if(c[i][j]=='*')px=i,py=j;
ans=0;
for(j=k; j<=15&&ans==0; j++) //迭代加深
trys(0,k,px,py,0);
if(ans>0)
printf("%d\n",ans);
else
printf("%d\n",-1);
}
}