USACO Magic Squares

1、这是96年的一道IOI题目,一道恶心的搜索。。。最近USACO快做哭了,根本没把我当初学者T T。。。谁说“做完前三章,稳拿一等奖”啊,岂止一等奖,明明可以参加NOI了。。。

2、刚开始我脑子发昏,竟然倒序输出,所以怎么都不对,没法输出字典序最小,还以为是搜索顺序的问题,硬是把程序改复杂了。。。

3、算法嘛,就是bfs+康托展开+逆康托展开即可。注意题目给出的数字顺序和常规不太一样,初始状态应是1,2,3,4,8,7,6,5。。。

4、对换行符要求也很严格,即使是0步,也要输出一个换行符。。。USACO你够了。。。

/*
ID:mrxy564
PROG:msquare
LANG:C++
*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=9;
struct state{
    char dir;
 int parent;
 int val;
 int num;
};
state s,e,q[41000];
int cnt=0,front,rear,Cnt;
char ans[41000],minans[41000];
bool flag=false,vis[41000];
int factorial[MAXN];
void intToArray(int x,int a[MAXN]){
    bool used[MAXN];
 int i,j,temp;
 for(i=1;i<=8;i++) used[i]=false;
 for(i=1;i<=8;i++){
     temp=x/factorial[8-i];
  for(j=1;j<=8;j++) if(!used[j]){
      if(temp==0) break;
   temp--;
  }
  a[i]=j;
  used[j]=true;
  x%=factorial[8-i];
 }
}
int arrayToInt(int a[MAXN]){
    int ans,i,j,temp;
 ans=0;
 for(i=1;i<=8;i++){
     temp=a[i]-1;
  for(j=1;j<i;j++) if(a[j]<a[i]) temp--;
  ans+=factorial[8-i]*temp;
 }
 return ans;
}
void init(){
 factorial[0]=1;
 for(int i=1;i<8;i++)
  factorial[i]=factorial[i-1]*i;
 for(int i=0;i<41000;i++)
  minans[i]='C';
 int a[MAXN]={0,1,2,3,4,8,7,6,5};
 s.val=arrayToInt(a);
 for(int i=1;i<=4;i++)
       scanf("%d",&a[i]);
 for(int i=8;i>=5;i--)
    scanf("%d",&a[i]);
 e.val=arrayToInt(a);
    memset(vis,0,sizeof(vis));
 return;
}
void change(int st,char dir,int pa){
 int a[MAXN];
 intToArray(st,a);
    if(dir=='A'){
     for(int i=1;i<=4;i++)
   swap(a[i],a[i+4]);
 }
 if(dir=='B'){
  int x1=a[1],x2=a[5];
     for(int i=4;i>=1;i--){
      int temp=i%4+1;
   a[temp]=a[i];
   a[temp+4]=a[i+4];
  }
  a[2]=x1;a[6]=x2;
 }
    if(dir=='C'){
     int x3=a[2];
  a[2]=a[6];
  a[6]=a[7];
  a[7]=a[3];
  a[3]=x3;
 }
 int temp=arrayToInt(a);
 if(!vis[temp]){
     vis[temp]=true;
  state news;
  news.dir=dir;
  news.parent=pa;
  news.val=temp;
  news.num=++rear;
  q[rear]=news;
 }
 return;
}
void print(state st){
    if(st.val!=s.val){
     print(q[st.parent]);
  ans[cnt++]=q[st.num].dir;
 }
}
void bfs(){
    front=-1;rear=0;
 vis[s.val]=true;
 q[0]=s;
 while(front<rear&&!flag){
     int temp=q[++front].val;
  while(temp==e.val&&front<=rear){
   e=q[front];
   print(e);
   if(strcmp(ans,minans)<0){
           for(int i=0;i<cnt;i++)
           minans[i]=ans[i];
     Cnt=cnt;
     cnt=0;
         }
      flag=true;
   temp=q[++front].val;
  }
  if(flag) break;
        change(temp,'A',front);
  change(temp,'B',front);
  change(temp,'C',front);
 }
 return;
}
int main(){
 freopen("msquare.in","r",stdin);
 freopen("msquare.out","w",stdout);
    init();
    bfs();
 if(flag){
    int num=0;
    printf("%d\n",Cnt);
    for(int i=0;i<Cnt;i++){
     printf("%c",minans[i]);
     num++;
     if(num%60==0) printf("\n");
    }
    if(num%60!=0||Cnt==0) printf("\n");
 }
 return 0;
}

官方题解:

This is a shortest path problem, where the nodes of the graph are board arrangements, and edges are transformations. There are 8! = 40,320 possible board arrangements, so the problem can be solved using breadth-first search (since all edges are of unit length.

Number the boards in increasing order lexicographically, so that indexing is simpler.

In order to simplify the calculations, walk the path backward (start at the ending arrangement given, find the minimum path to the initial configuration following reverse transformations). Then, walk backwards in the resulting tree to determine the path.

#include <stdio.h>
#include <assert.h>

/* the distance from the initial configuration (+1) */
/* dist == 0 => no know path */
int dist[40320];

/* calculate the index of a board */
int encode(int *board)
 {
  static int mult[8] = 
    {1, 8, 8*7, 8*7*6, 8*7*6*5,
     8*7*6*5*4, 8*7*6*5*4*3, 8*7*6*5*4*3*2};
  
  /* used to calculate the position of a number within the
     remaining ones */
  int look[8] = {0, 1, 2, 3, 4, 5, 6, 7};
  int rlook[8] = {0, 1, 2, 3, 4, 5, 6, 7};
  /* rlook[look[p]] = p and look[rlook[p]] = p */

  int lv, rv;
  int t;

  rv = 0;
  for (lv = 0; lv < 8; lv++)
   {
    t = look[board[lv]]; /* the rank of the board position */
    assert(t < 8-lv); /* sanity check */
    rv += t * mult[lv]; 

    assert(look[rlook[7-lv]] == 7-lv); /* sanity check */

    /* delete t */
    look[rlook[7-lv]] = t;
    rlook[t] = rlook[7-lv];
   }
  return rv;
 }

/* the set of transformations, in order */
static int tforms[3][8] = { {8, 7, 6, 5, 4, 3, 2, 1},
     {4, 1, 2, 3, 6, 7, 8, 5}, {1, 7, 2, 4, 5, 3, 6, 8} };

void do_trans(int *inboard, int *outboard, int t)
 { /* calculate the board (into outboard) that results from doing
      the t'th transformation to inboard */
  int lv;
  int *tform = tforms[t];

  assert(t >= 0 && t < 3);

  for (lv = 0; lv < 8; lv++)
    outboard[lv] = inboard[tform[lv]-1];
 }

void do_rtrans(int *inboard, int *outboard, int t)
 { /* calculate the board (into outboard) that which would result
      in inboard if the t'th transformation was applied to it */
  int lv;
  int *tform = tforms[t];

  assert(t >= 0 && t < 3);

  for (lv = 0; lv < 8; lv++)
    outboard[tform[lv]-1] = inboard[lv];
 }

/* queue for breadth-first search */
int queue[40325][8];
int qhead, qtail;

/* calculate the distance from each board to the ending board */
void do_dist(int *board)
 {
  int lv;
  int t1;
  int d, t;

  qhead = 0;
  qtail = 1;

  /* the ending board is 0 steps away from itself */
  for (lv = 0; lv < 8; lv++) queue[0][lv] = board[lv];
  dist[encode(queue[0])] = 1; /* 0 steps (+ 1 offset for dist array) */

  while (qhead < qtail)
   {
    t1 = encode(queue[qhead]);
    d = dist[t1];

    /* for each transformation */
    for (lv = 0; lv < 3; lv++)
     {
      /* apply the reverse transformation */
      do_rtrans(queue[qhead], queue[qtail], lv);

      t = encode(queue[qtail]);
      if (dist[t] == 0) 
       { /* found a new board position!  add it to queue */
        qtail++;
        dist[t] = d+1;
       }
     }

    qhead++;
   }
 }

/* find the path from the initial configuration to the ending board */
void walk(FILE *fout)
 {
  int newboard[8];
  int cboard[8];
  int lv, lv2;
  int t, d;

  for (lv = 0; lv < 8; lv++) cboard[lv] = lv;
  d = dist[encode(cboard)];
  /* start at the ending board */
  while (d > 1)
   {
    for (lv = 0; lv < 3; lv++)
     {
      do_trans(cboard, newboard, lv);
      t = encode(newboard);
      if (dist[t] == d-1) /* we found the previous board! */
       {
        /* output transformatino */
        fprintf (fout, "%c", lv+'A');

	/* find the rest of the path */
        for (lv2 = 0; lv2 < 8; lv2++) cboard[lv2] = newboard[lv2];
	break;
       }
     }
    assert(lv < 3);
    d--;
   }
  fprintf (fout, "\n");
 }

int main(int argc, char **argv)
 {
  FILE *fout, *fin;
  int board[8];
  int lv;

  if ((fin = fopen("msquare.in", "r")) == NULL)
   {
    perror ("fopen fin");
    exit(1);
   }
  if ((fout = fopen("msquare.out", "w")) == NULL)
   {
    perror ("fopen fout");
    exit(1);
   }

  for (lv = 0; lv < 8; lv++) 
   {
    fscanf (fin, "%d", &board[lv]);
    board[lv]--; /* use 0-based instead of 1-based */
   }

  /* calculate the distance from each board to the ending board */
  do_dist(board);

  for (lv = 0; lv < 8; lv++) board[lv] = lv;

  /* output the distance from and the path from the initial configuration */
  fprintf (fout, "%d\n", dist[encode(board)]-1);
  walk(fout);

  return 0;
 }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值