题意:
给你一个被打乱的数独(原来是正确的),这个打乱的规则是逆时针旋转了数独中一些4*4的块几次。
问你,从正确的到被打乱的状态,这个数独总共至少被逆时针旋转了多少次
解析:
官方题解:
搜索加可行性剪枝即可通过。由于数独限制较强,剪枝效果良好。
。。。。。。。。。。。。。。。。。。。。。。
不得不说这道题真的是玄学复杂度...真的不得不佩服比赛的过的人
与大佬的差距不止在脑子,还在胆量....
题意说被逆时针打乱,那么我们就顺时针把它复原,看至少总共需要多少次把它完成
我用的搜索方法就是遍历每一个块的旋转方向,理论上的复杂度就是4^16
。。。但是同时你要维护数独的性质,一旦你旋转到的一个块无论如何都不满足数独的性质,
那么就要回溯,推倒原来块的旋转方向,来找正解。这样复杂度好像是可行的........
题意说
我这里就用0..15来标记每一个块,然后搜索就是从0->15,然后用vis来标记每一行、每一列的各个值存在的状态
一旦一个块不满足数独性质,一直回溯到之前的另一个块的某一个旋转方向使这个块旋转满足性质。
#include <cstdio>
#include <cstring>
#include <bitset>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 20;
const ll INF = 0x3f3f3f3f;
int dir[MAXN][2]={0,0,0,4,0,8,0,12,4,12,4,8,4,4,4,0,8,0,8,4,8,8,8,12,12,12,12,8,12,4,12,0}; //每一个块相对于第一个块x,y的变化
char str[MAXN][MAXN];
char news[MAXN][MAXN];
bitset<MAXN> visr[MAXN],visc[MAXN];
void cal0(int id)
{
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++)
{
news[dir[id][0]+i][dir[id][1]+j]=str[dir[id][0]+i][dir[id][1]+j];
}
}
void cal1(int id)
{
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++)
{
news[dir[id][0]+j][dir[id][1]+4-i+1]=str[dir[id][0]+i][dir[id][1]+j];
}
}
void cal2(int id)
{
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++)
{
news[dir[id][0]+4-i+1][dir[id][1]+4-j+1]=str[dir[id][0]+i][dir[id][1]+j];
}
}
void cal3(int id)
{
for(int i=1;i<=4;i++)
for(int j=1;j<=4;j++)
{
news[dir[id][0]+4-j+1][dir[id][1]+i]=str[dir[id][0]+i][dir[id][1]+j];
}
}
int check(bitset<MAXN> vis1[],bitset<MAXN> vis2[],int id,bitset<MAXN> pre1[],bitset<MAXN> pre2[])
{
int addx=dir[id][0];
int addy=dir[id][1];
for(int i=1;i<=4;i++)
{
for(int j=1;j<=4;j++)
{
if(news[addx+i][addy+j]>='A') vis1[i].set(news[addx+i][addy+j]-'A'+10);
else vis1[i].set(news[addx+i][addy+j]-'0');
}
if((vis1[i]&pre1[i])!=0)
{
return 0;
}
for(int j=1;j<=4;j++)
{
if(news[addx+j][addy+i]>='A')vis2[i].set(news[addx+j][addy+i]-'A'+10);
else vis2[i].set(news[addx+j][addy+i]-'0');
}
if((vis2[i]&pre2[i])!=0)
{
return 0;
}
}
return 1;
}
ll ans=INF;
int dfs(int id,ll val)
{
if(id>=16)
{
ans=min(ans,val);
//return 1;
return 0;
}
int addx=dir[id][0];
int addy=dir[id][1];
bitset<MAXN> pre1[5],pre2[5]; //保存在这个块之前的各行、列的状态
bitset<MAXN> vis1[5],vis2[5]; //记录当前块各行、列的状态
for(int i=1;i<=4;i++)
{
pre1[i]=visr[i+addx];
pre2[i]=visc[i+addy];
}
int flag=1;
for(int i=0;i<4;i++)
{
if(i==0)cal0(id);
else if(i==1) cal1(id);
else if(i==2) cal2(id);
else cal3(id);
flag=check(vis1,vis2,id,pre1,pre2);
if(flag)
{
for(int i=1;i<=4;i++) //总的行列状态
{
visr[i+addx]|=vis1[i];
visc[i+addy]|=vis2[i];
}
flag=dfs(id+1,val+i);
if(flag) return 1;
for(int i=1;i<=4;i++) //回溯状态
{
visr[i+addx]=pre1[i];
vis1[i].reset();
visc[i+addy]=pre2[i];
vis2[i].reset();
}
}
else
{
for(int i=1;i<=4;i++)
{
vis1[i].reset();
vis2[i].reset();
}
}
}
return 0;
}
int main()
{
int t;
int cas=0;
scanf("%d",&t);
while(t--)
{
for(int i=1;i<=16;i++)
{
getchar();
scanf("%s",str[i]+1);
}
ans=INF;
dfs(0,0);
printf("%lld\n",ans);
}
return 0;
}