社会我赞哥
说在前面
虽然最近有些自闭,但是该来的总是要来的。
yuzan1830给出了一个的网格,每个格子内填了内的一个数字,且每个数字恰好被填一次。现在有两种类型的操作可以进行:
- 任选一个数字,再从上下左右与它相邻的数字中选一个,两个数字进行交换。即两个数字所在的行数相差、列数相同,或列数相差、行数相同。
- 任选一个数字,再从左上、左下、右上、右下与它相邻的数字中选一个,两个数字进行交换。即两个数字所在的行数和列数都相差。
为了增加难度,yuzan1830规定每一次操作不能和上一次操作属于同一种类型,即两种操作必须交替进行。而第一次操作可以从两种类型中任选一种。
现在需要让网格中的数字从上到下、从左到右按的顺序排列,求所需的最少步数,并输出任意一种方案。输出的第行应包括第次操作交换的两个数字。步数为输出"No Need",无解输出"No Solution"。
样例输入
1 2 9
4 6 3
7 8 5
样例输出
Step #1: 3 9
Step #2: 5 6
Step #3: 6 9
接受挑战
先考虑一下的情况。把的网格展开成一个长为的序列,由于,网格中数字的摆放顺序不会超过种,宽搜 表示毫无压力。
每一次操作前网格中数字的摆放顺序定义成一个状态,作为图中的结点;能够相互转化的顺序之间连边以转移状态。图中每个结点第一次到达时遍历的层数一定是最少的,若遇到已经访问过的结点应该停止往下搜索。标记已经访问过的结点,康托展开 表示毫无压力。
状态数:
const int maxn=362880;
一个状态应该包括个格子内的数字,图方便的话可以这样写:
typedef int state[10];
但是这样写起来方便用起来不方便,我们最好还是装一下逼:
struct state{
int st[10];
state operator=(int arr[10]){
memcpy(st,arr,10);return *this;
}
int &operator[](int x){return st[x];}
};
然后是康托展开部分。这样就有,之类的映射关系了。
int fact[]={1,1,2,6,24,120,720,5040,40320,362880};
int encode(state st){
int cnt=(st[1]-1)*fact[8];
for(int i=2;i<=9;i++){
int tmp=0;
for(int j=1;j<i;j++)tmp+=st[j]<st[i];
cnt+=(st[i]-tmp-1)*fact[9-i];
}
return cnt+1;
}
state decode(int c){
state ans;int vis=0;c--;
for(int i=1;i<=9;i++){
int j,t=c/fact[9-i];
for(j=1;j<=9;j++)if(!(vis>>j&1)&&!t--)break;
ans[i]=j,vis|=1<<j,c%=fact[9-i];
}
return ans;
}
然后是核心部分:宽搜。由于第一步操作有两种选择,因此要搜两次,而在两次都搜完之前是不确定最优解的,因此要把两次搜索的结果都存下来,这里本人偷懒用了一个类。
宽搜前的声明:
- 标记状态(是值)是否被访问过。
- 记录状态上一步的状态。
- 记录从初始状态到达状态所需的步数。
- 记录从状态转移到状态选择的操作类型。
- 记录从状态转移到状态交换的数字对。
:从状态搜索,且第一步的操作类型为。两次搜索的值不同。
:递归输出从初始状态到状态的解。
int dx1[]={-1,1,0,0},dy1[]={0,0,-1,1};
int dx2[]={-1,-1,1,1},dy2[]={-1,1,-1,1};
class solver{
public:
bool vis[maxn];
int fa[maxn],step[maxn],type[maxn];
pair<int,int> opt[maxn];
void bfs(state st,int type_st){
memset(vis,0,sizeof(vis));
int u=encode(st),v;
state cur,next;
vis[u]=1,fa[u]=-1,step[u]=0;
type[u]=type_st^1;
queue<int> q;q.push(u);
while(!q.empty()){
u=q.front();q.pop();
if(u==1)return;
cur=decode(u);
for(int x=1;x<=3;x++){
for(int y=1;y<=3;y++){
int pos=(x-1)*3+y;
for(int i=0;i<4;i++){
int tx,ty;
if(type[u])tx=x+dx2[i],ty=y+dy2[i];
else tx=x+dx1[i],ty=y+dy1[i];
int newpos=(tx-1)*3+ty;
if(tx>0&&tx<=3&&ty>0&&ty<=3){
next=cur;swap(next[pos],next[newpos]);
v=encode(next);
if(!vis[v]){
vis[v]=1,fa[v]=u,step[v]=step[u]+1;
type[v]=type[u]^1;
opt[v]=make_pair(next[pos],next[newpos]);
q.push(v);
}
}
}
}
}
}
}
void print_path(int u){
if(fa[u]<0)return;
print_path(fa[u]);
printf("Step #%d: %d %d\n",step[u],opt[u].first,opt[u].second);
}
};
solver g1,g2;
主函数就比较显然了:
int main(){
state st;
for(int i=1;i<=9;i++)scanf("%d",&st[i]);
g1.bfs(st,0);
g2.bfs(st,1);
if(!g1.vis[1]&&!g2.vis[1])printf("No Solution\n");
else if(!g2.vis[1]||g1.step[1]<g2.step[1]){
if(!g1.step[1])printf("No Need\n");
else{g1.print_path(1);printf("\n");}
}
else{
if(!g2.step[1])printf("No Need\n");
else{g2.print_path(1);printf("\n");}
}
return 0;
}
可能有多解的情况。比如一开始的样例的另一个解为:
Step #1: 6 9
Step #2: 3 6
Step #3: 5 9
分析一下时间复杂度:对于边长为的网格,一共有不超过种状态,每种状态可以选择个位置,每个位置又可以选择另外个位置的数来交换,而一次康托展开或逆康托展开的复杂度为,因此一次搜索的时间复杂度为。看起来很可怕,但在实际情况下很多状态都是达不到的,因此这个上界特别松。
更多挑战
一旦,状态数就达到了种。先不论时间的问题,首先康托展开就不再适用。
来源:https://blog.csdn.net/PHenning/article/details/100164339