Solution
一道神仙暴力剪枝题,思路是在9*9的数独之上,再多添加3个剪枝
1.判断每个空格中,如果一个字母都填不了就返回,如果只能填一个,就填上并继续搜索
2.对于每个字母,在每行\列\16宫格中判断能填的位置,如果没有就返回,如果只有一个就填上,并继续搜索
3.在上述剪枝完成后,再用位运算优化,取出最少的一个空格,并用lowbit运算取出能填的数
Attention!!!
1.宫格是真的恶心……
2.注意填过了和不能填的区别
详细见代码
#include<bits/stdc++.h> using namespace std; #define lb(a) (a&-a) const int N=(1<<16); char s[20][20]; int ok[20][20],cnt[N],num[N],kase,tot;//ok数组2的第i位为1表示可以填写 bool print(){for(int i=1;i<=16;i++)printf("%s\n",s[i]+1);return false;} void clear(){tot=0;for(int i=1;i<=16;i++)for(int j=1;j<=16;j++)ok[i][j]=N-1;} void upd(int x,int y,int num){//更新的函数 for(int i=1;i<=16;i++){ ok[x][i]&=~(1<<num);//行 ok[i][y]&=~(1<<num);//列 } for(int i=(x-1)/4*4+1;i<=(x-1)/4*4+4;i++)//宫格 for(int j=(y-1)/4*4+1;j<=(y-1)/4*4+4;j++) ok[i][j]&=~(1<<num); } bool dfs(int step){ if(step==tot+1) return print(); int ansi,ansj,mn=1e9; int ok2[20][20]; memcpy(ok2,ok,sizeof(ok2));//因为此处操作比较复杂,所以我们先把数组复制到临时数组中,之后再还原 for(int i=1;i<=16;i++) for(int j=1;j<=16;j++){ if(s[i][j]!=‘-‘)continue;//不是空格 if(!ok[i][j])return true;//不能填 if(cnt[ok[i][j]]==1){//只有一个可以填,就是剪枝1 s[i][j]=num[ok[i][j]]+‘A‘,upd(i,j,num[ok[i][j]]); if(!dfs(step+1))return false; s[i][j]=‘-‘; memcpy(ok,ok2,sizeof(ok2)); return true; } if(cnt[ok[i][j]]<mn){//顺便找出空格最少的 mn=cnt[ok[i][j]]; ansi=i,ansj=j; } } for(int i=0;i<16;i++){//剪枝2,行的剪枝 for(int j=1;j<=16;j++){ int vis=0,nxt=0; bool fs=0; for(int k=1;k<=16;k++){ if(s[j][k]==‘A‘+i)fs=1;//记得特判,填过了和不能填的差别 if((ok[j][k]>>i&1)&&s[j][k]==‘-‘) ++vis,nxt=k; } if(fs)continue;//填过了就跳过 if(!vis)return true;//如果不可填就不行 if(vis==1){ s[j][nxt]=i+‘A‘;upd(j,nxt,i); if(!dfs(step+1))return false; memcpy(ok,ok2,sizeof(ok2)); s[j][nxt]=‘-‘; return true; } } } for(int i=0;i<16;i++){//列的剪枝 for(int k=1;k<=16;k++){ int vis=0,nxt=0; bool fs=0; for(int j=1;j<=16;j++){ if(s[j][k]==‘A‘+i)fs=1; if((ok[j][k]>>i&1)&&s[j][k]==‘-‘) ++vis,nxt=j; } if(fs==1)continue; if(!vis)return true; if(vis==1){ s[nxt][k]=i+‘A‘,upd(nxt,k,i); if(!dfs(step+1))return false; s[nxt][k]=‘-‘; memcpy(ok,ok2,sizeof(ok2)); return true; } } } for(int k=0;k<16;k++){//九宫格的剪枝 for(int x=1;x<=13;x+=4) for(int y=1;y<=13;y+=4){ int nt=0,nxti=0,nxtj=0; bool fs=0; for(int i=x;i<x+4;++i) for(int j=y;j<y+4;++j){//zz错误 if(s[i][j]==‘A‘+k)fs=1; if((ok[i][j]>>k&1)&&s[i][j]==‘-‘) ++nt,nxti=i,nxtj=j; if(nt>1)break; } if(fs)continue; if(!nt)return true; if(nt==1){ s[nxti][nxtj]=k+‘A‘,upd(nxti,nxtj,k); if(!dfs(step+1))return false; s[nxti][nxtj]=‘-‘; memcpy(ok,ok2,sizeof(ok2)); return true; } } } for(int tmp=ok[ansi][ansj];tmp;tmp-=lb(tmp)){//找最小的空格去填 s[ansi][ansj]=num[lb(tmp)]+‘A‘; upd(ansi,ansj,num[lb(tmp)]); if(!dfs(step+1))return false; s[ansi][ansj]=‘-‘;//还原 memcpy(ok,ok2,sizeof(ok)); } return true; } void init(){//输入 clear(); for(int i=1;i<=16;i++) scanf("%s", s[i]+1);//+1是指下标从一开始,比较方便 for(int i=1;i<=16;i++) for(int j=1;j<=16;j++) if(s[i][j]!=‘-‘) upd(i,j,s[i][j]-‘A‘);//如果不是空格就更新 else ++tot;//否则计数 dfs(1); } int main(){ int test; cin>>test; for(int i=0;i<16;i++)num[1<<i]=i;//预处理2的次数幂与对数的关系 for(int i=0;i<(1<<16);i++) for(int j=i;j;j-=lb(j)) cnt[i]++;//预处理每一个数的1的个数 while(test--){ if(kase++)puts(""); init(); } }
原文地址:https://www.cnblogs.com/coder-cjh/p/11524218.html
时间: 2024-11-09 10:02:01