来自yuzan1830的挑战(三)

只谈情不闲聊 提交于 2019-11-29 00:49:14

社会我赞哥

说在前面

虽然最近有些自闭,但是该来的总是要来的。
yuzan1830给出了一个n×nn\times n的网格,每个格子内填了[1,n2][1,n^2]内的一个数字,且每个数字恰好被填一次。现在有两种类型的操作可以进行:

  • 任选一个数字,再从上下左右与它相邻的数字中选一个,两个数字进行交换。即两个数字所在的行数相差11、列数相同,或列数相差11、行数相同。
  • 任选一个数字,再从左上、左下、右上、右下与它相邻的数字中选一个,两个数字进行交换。即两个数字所在的行数和列数都相差11

为了增加难度,yuzan1830规定每一次操作不能和上一次操作属于同一种类型,即两种操作必须交替进行。而第一次操作可以从两种类型中任选一种。
现在需要让网格中的数字从上到下、从左到右按1,2,,n21,2,\dots,n^2的顺序排列,求所需的最少步数,并输出任意一种方案。输出的第ii行应包括第ii次操作交换的两个数字。步数为00输出"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

接受挑战

先考虑一下n=3n=3的情况。把n×nn\times n的网格展开成一个长为n2n^2的序列,由于9!=3628809!=362880,网格中数字的摆放顺序不会超过362880362880种,宽搜 表示毫无压力。
每一次操作前网格中数字的摆放顺序定义成一个状态,作为图中的结点;能够相互转化的顺序之间连边以转移状态。图中每个结点第一次到达时遍历的层数一定是最少的,若遇到已经访问过的结点应该停止往下搜索。标记已经访问过的结点,康托展开 表示毫无压力。

状态数:

const int maxn=362880;

一个状态应该包括99个格子内的数字,图方便的话可以这样写:

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];}
};

然后是康托展开部分。这样就有encode({6,5,2,8,7,9,1,2,4})=223051\text{encode(\{6,5,2,8,7,9,1,2,4\})}=223051decode(223051)={6,5,2,8,7,9,1,2,4}\text{decode(223051)}=\{6,5,2,8,7,9,1,2,4\}之类的映射关系了。

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;
}

然后是核心部分:宽搜。由于第一步操作有两种选择,因此要搜两次,而在两次都搜完之前是不确定最优解的,因此要把两次搜索的结果都存下来,这里本人偷懒用了一个类。
宽搜前的声明:

  • vis[x]vis[x]标记状态xxxxencode\text{encode}值)是否被访问过。
  • fa[x]fa[x]记录状态xx上一步的状态。
  • step[x]step[x]记录从初始状态到达状态xx所需的步数。
  • type[x]type[x]记录从状态fa[x]fa[x]转移到状态xx选择的操作类型。
  • opt[x]opt[x]记录从状态fa[x]fa[x]转移到状态xx交换的数字对。

bfs(st,typest)\text{bfs(}st,type_{st}\text):从状态stst搜索,且第一步的操作类型为typesttype_{st}。两次搜索的typesttype_{st}值不同。
print_path(u)\text{print\_path(}u\text):递归输出从初始状态到状态uu的解。

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

分析一下时间复杂度:对于边长为nn的网格,一共有不超过(n2)!(n^2)!种状态,每种状态可以选择n2n^2个位置,每个位置又可以选择另外44个位置的数来交换,而一次康托展开或逆康托展开的复杂度为O(n4)O(n^4),因此一次搜索的时间复杂度为O((n2)!4n6)O((n^2)!\cdot 4n^6)。看起来很可怕,但在实际情况下很多状态都是达不到的,因此这个上界特别松。

更多挑战

一旦n=4n=4,状态数就达到了16!=2092278988800016!=20922789888000种。先不论时间的问题,首先康托展开就不再适用。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!