9.15
伤心消消忧
这种在一个图形里一步一步操作,求最少步骤的题,一般是搜索。显然不能DP,不能启发式,数据范围又小,所以暴力深搜。
先保存当前图的状态,以便后来回溯。以此图为状态,枚举每一个点,移动。移动后模拟方块下落和方块消除,直到方块不能再消除,方块下落才停止。
剪枝:
-
交换两个颜色相同的块没有意义
-
一个块的左边是非空块时不需要考虑左移,因为会和之前的块右移重复,即只有当左块为空时才左移
-
根据题目优先度的排序,可以知道,右移优先于左移,所以在dfs时先考虑右移
上码
#include<bits/stdc++.h> using namespace std; int n,mp[6][8],ans[6][3],tmp[6][6][8],xiao[6][8]; inline bool pd(){//底下一层没有块 for(int i=0;i<5;++i) if(mp[i][0]) return 0; return 1; } inline void update(){ for(int i=0;i<5;++i){ int wow=0; for(int j=0;j<7;++j){ if(!mp[i][j]) ++wow; else{ if(!wow)continue; mp[i][j-wow]=mp[i][j]; mp[i][j]=0; } } } return ; } bool disa(){ bool flag=0; for(int i=0;i<5;++i) for(int j=0;j<7;++j){ if(!mp[i][j]) continue; if(i<3)//消除的块需要标记,之后再清除 if( mp[i][j]==mp[i+1][j]&&mp[i+2][j]==mp[i+1][j]){ flag=1;xiao[i][j]=xiao[i+1][j]=xiao[i+2][j]=1; } if(j<5) if( mp[i][j]==mp[i][j+1]&&mp[i][j+2]==mp[i][j]){ flag=1;xiao[i][j]=xiao[i][j+1]=xiao[i][j+2]=1; } } for(int i=0;i<5;++i) for(int j=0;j<7;++j){ if(xiao[i][j]==1){ mp[i][j]=0; xiao[i][j]=0; } } return flag; } void move(int x,int y,int f){ swap(mp[x][y],mp[x+f][y]); update(); while(disa()) update();//下落,直到不能消除 return ; } void dfs(int dep){ if(pd()){ for(int i=1;i<dep;++i) printf("%d %d %d\n",ans[i][0],ans[i][1],ans[i][2]); exit(0); } if(dep==n+1) return; memcpy(tmp[dep],mp,sizeof(mp));//用tmp记录每一步执行前的状态,方便回溯 for(int i=0;i<5;++i) for(int j=0;j<7;++j){ if(mp[i][j]){ if(i<4&&mp[i+1][j]!=mp[i][j]){ move(i,j,1);//移动并记录,用于输出答案 ans[dep][0]=i; ans[dep][1]=j; ans[dep][2]=1; disa(); dfs(dep+1); memcpy(mp,tmp[dep],sizeof(tmp[dep])); } if(i){ if(mp[i-1][j]==0){//只有当左边没有块时才左移 move(i,j,-1); ans[dep][0]=i; ans[dep][1]=j; ans[dep][2]=-1; disa(); dfs(dep+1); memcpy(mp,tmp[dep],sizeof(tmp[dep])); } } } } return; } int main(){ scanf("%d",&n); for(int i=0;i<5;++i) for(int j=0;j<=7;++j){ scanf("%d",&mp[i][j]); if(!mp[i][j]) break; } dfs(1); puts("-1"); return 0; } /* 2 1 0 1 0 2 2 0 1 0 2 0 */
数独
同是深搜+模拟,这是高级版数独,需要优化.
优化:
-
状态压缩,用三个数组维护每行、每列、每个九宫格能填的数字,对于点 (i,j)
tmp=h[i]&l[j]&g[GG(i,j)];
,tmp就是能填的数,如(000001011)能填的数就是1、2、4.所以需要预处理log(2). -
和正常人做数独一样,每次需要找到能填的数最少(>=1)的格子填(深搜前for循环找到这个格子,所以需要预处理每个状态(二进制数)中的1的个数),然后选择它的lowbit开始搜索
好码好码好码好码
#include<bits/stdc++.h>
#define fuck puts("fuck");
#define GG(x,y) (3*((x+2)/3-1)+(y+2)/3)
using namespace std;
int h[10],l[10],g[10],mp[10][10],can[10][10],lg[533],ww[533];
char s[90];
int kepu(int x){
int ret=0;
for(;x;x>>=1)
if(x&1) ++ret;
return ret;
}
void print(){
for(int i=1;i<=9;++i)
for(int j=1;j<=9;++j)
printf("%d",mp[i][j]);
putchar('\n');
}
bool dfs(){
int tmp,nnn,minn=10,posx,posy;
for(int i=1;i<=9;++i)
for(int j=1;j<=9;++j){
if(mp[i][j]) continue;
tmp=h[i]&l[j]&g[GG(i,j)];
nnn=ww[tmp];
if(nnn<minn) {
minn=nnn;
posx=i;
posy=j;
}
}
if(minn==0) return 0;
if(minn==10){//都填满了,结束
print();
return 1;
}
tmp=h[posx]&l[posy]&g[GG(posx,posy)];
for(;tmp;tmp=tmp-(tmp&(-tmp))){
int i=tmp&(-tmp);//标记选择
mp[posx][posy]=lg[i];
h[posx]^=i;
l[posy]^=i;
g[GG(posx,posy)]^=i;
if(dfs()) return 1;//回溯
mp[posx][posy]=0;
h[posx]^=i;
l[posy]^=i;
g[GG(posx,posy)]^=i;
}
return 0;
}
void init(){
for(int i=1;i<=9;++i)
h[i]=l[i]=g[i]=511;//(111111111)2
for(int i=1,j=1;i<=512;i<<=1,++j)
lg[i]=j;//求1在第几个,如lg[16]=4(因为16=(1000)2)
for(int i=1;i<=511;++i)
ww[i]=kepu(i);//二进制下有多少个数码是1
}
int main(){
scanf("%s",s+1);
while(s[1]!='e'){
init();
for(int i=1;i<=9;++i){
for(int j=1;j<=9;++j){
if(s[(i-1)*9+j]!='.'){
mp[i][j]=s[(i-1)*9+j]-'0';
h[i]=h[i]^(1<<(mp[i][j]-1));
l[j]=l[j]^(1<<(mp[i][j]-1));
g[GG(i,j)]=g[GG(i,j)]^(1<<(mp[i][j]-1));
}
else mp[i][j]=0;
}
}
dfs();
scanf("%s",s+1);
}
}
/*
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
.2738..1..1...6735.......293.5692.8...........6.1745.364.......9518...7..8..6534.
end
*/