P4289 [HAOI2008]移动玩具 & P1225 黑白棋游戏
P4289 [HAOI2008]移动玩具 & P1225 黑白棋游戏
题目:
在一个 \(4 \times 4\) 的方框内摆放了若干个相同的玩具,某人想将这些玩具重新摆放成为他心中理想的状态,规定移动时只能将玩具向上下左右四个方向移动,并且移动的位置不能有玩具,请你用最少的移动次数将初始的玩具状态移动到某人心中的目标状态。(同时记录路径)
Sol:
标签是 hash
,可是我不会。我们看,棋盘大小就 \(4 \times 4\),固定的,想状压可行。那么难点就在于二维数组与二进制的转换。
对于这个矩形:\(1001\ 1011\ 0100\ 0100\),我们把它一行一行列出来,依次表示二进制的第 \(i\) 位,就上面这个矩形,二进制就是 0010001011011001。我们观察一下规律:
(下面第一个数意思是行,第二个是列,第三个是表示 \(2^k\))
\(1 \ 1 \to 2^0,1 \ 2 \to 2^1,1 \ 3 \to 2^2,1 \ 4 \to 2^3,2 \ 1 \to 2^4,2 \ 2 \to 2^5\)
这能看出甚么?好像能推出两条柿子,任选一个喽。
\(k =(i-1) \times 4+(j-1) \ \text{或者}\ k=(4 \times i)-(4-j+1)\)
那么这题就做完了,广搜一下就好了。
Code:
/*
Knowledge : Rubbish Algorithm
Work by : Gym_nastics
Time : O(AC)
*/
#include<cmath>
#include<queue>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int INF=0x3f3f3f3f;
const int Mod=1e9+7;
const int N=1e6+6;
int read() {
int x=0,f=0;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) f|=(ch=='-');
for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch&15);
return f?-x:x;
}
void print(int x) {
if(x<0) putchar('-'),x=-x;
if(x>9) print(x/10);
putchar(x%10+48);
}
int dx[]= {1,-1,0,0};
int dy[]= {0,0,1,-1};
int A[5][5],B[5][5],ST,To,f[5][5];
int reverse_one(int M[5][5]){
int res=0;
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++)
res+=M[i][j]*(1<<((4*i)-(4-j+1)));
return res;
}
void reverse_two(int k){
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++)
f[i][j]=(k>>((4*i)-(4-j+1))&1);
}
int dis[N],vis[N];
void BFS(){queue<int>q;
vis[ST]=1;dis[ST]=0;q.push(ST);
while(!q.empty()){
int u=q.front();q.pop();
if(u==To) return print(dis[u]),void();
reverse_two(u);
for(int i=1;i<=4;i++){
for(int j=1;j<=4;j++){
if(!f[i][j]) continue;
for(int k=0;k<4;k++){
int xx=i+dx[k],yy=j+dy[k];
if(xx>=1&&yy>=1&&xx<=4&&yy<=4&&!f[xx][yy]){
f[xx][yy]=1;f[i][j]=0;
int Mi=reverse_one(f);
if(!vis[Mi]) vis[Mi]=1,dis[Mi]=dis[u]+1,q.push(Mi);
f[xx][yy]=0,f[i][j]=1;
}
}
}
}
}
return;
}
signed main() {
for(int i=1;i<=4;i++){
for(int j=1;j<=4;j++){
char ch;scanf(" %c",&ch);
f[i][j]=A[i][j]=ch-'0';
}
}
for(int i=1;i<=4;i++){
for(int j=1;j<=4;j++){
char ch;scanf(" %c",&ch);
B[i][j]=ch-'0';
}
}
ST=reverse_one(A);
To=reverse_one(B);
return BFS(),0;
}
黑白棋游戏只多了一个记录路径。但是状压之后怎么记录路径?我们可以存下现在状压数是由哪个状压数搜过来的,而且这两个数对应的二进制只有两个位置不一样,并且二进制最长 \(16\) 位,感觉可以暴力找位置。 但是在 zcxxxxx 大佬的协助之下发现暴力并不可做……另寻他法。直接巨降智,如上代码在找下个状态的时候就是根据位置来的,只需要开个结构体存下就好了,到时候递归输出。
void Print(int i){
if(i==ST) return;Print(pre[i]);
print(r[i].nx);print(r[i].ny);
print(r[i].px);print(r[i].py);
putchar('\n');
}
void BFS(){queue<int>q;
vis[ST]=1;dis[ST]=0;q.push(ST);
while(!q.empty()){
int u=q.front();q.pop();
if(u==To) return print(dis[u]),putchar('\n'),Print(u),void();
reverse_two(u);
for(int i=1;i<=4;i++){
for(int j=1;j<=4;j++){
if(!f[i][j]) continue;
for(int k=0;k<4;k++){
int xx=i+dx[k],yy=j+dy[k];
if(xx>=1&&yy>=1&&xx<=4&&yy<=4&&!f[xx][yy]){
f[xx][yy]=1;f[i][j]=0;
int Mi=reverse_one(f);
if(!vis[Mi]){
vis[Mi]=1;dis[Mi]=dis[u]+1;q.push(Mi);pre[Mi]=u;
r[Mi].nx=i;r[Mi].ny=j;
r[Mi].px=xx;r[Mi].py=yy;
}
f[xx][yy]=0,f[i][j]=1;
}
}
}
}
}
return;
}