1. 程式人生 > >[BZOJ]1085 騎士精神(SCOI2005)

[BZOJ]1085 騎士精神(SCOI2005)

白色 bre 想法 轉移 cst 自己 string typedef hash

  這種鮮明的玄學風格很明顯就是十幾年前的題目。

Description

  在一個5×5的棋盤上有12個白色的騎士和12個黑色的騎士, 且有一個空位。在任何時候一個騎士都能按照騎士的走法(它可以走到和它橫坐標相差為1,縱坐標相差為2或者橫坐標相差為2,縱坐標相差為1的格子)移動到空位上。 給定一個初始的棋盤,怎樣才能經過移動變成如下目標棋盤: 為了體現出騎士精神,他們必須以最少的步數完成任務。

  技術分享

Input

  第一行有一個正整數T,表示一共有N組數據。接下來有T個5×5的矩陣,0表示白色騎士,1表示黑色騎士,*表示空位。兩組數據之間沒有空行。

Output

  對於每組數據都輸出一行。如果能在15步以內(包括15步)到達目標狀態,則輸出步數,否則輸出-1。

Sample Input

  2
  10110
  01*11
  10111
  01001
  00000
  01011
  110*1
  01110
  01010
  00100

Sample Output

  7
  -1

HINT

  T<=10。

Solution

  拿到這道題應該沒有別的想法吧,於是我們直接開始往搜索去想。

  由於步數最多只有15,所以最暴力的暴力的時間復雜度為O(T*25*815)。

  如果用A*算法解決這道題,估價函數為不符合答案的格子個數,如果剩余步數小於這個值,那麽這個狀態一定不能在15步內走到答案。

  復雜度似乎可以不用乘上25,所以理論時間復雜度上限為O(T*815)。

  所以A*算法就是這麽神奇地通過該題的,網絡上題解有很多,小C就不再贅述。

  都是玄學。

  所以小C還是介紹一下自己的折半搜索做法吧。

  從目標狀態往前搜8步,用一個map把每個狀態hash的答案存下來。

  從每個起始狀態往後搜7步,對於每個狀態到map查找並更新答案。

  理論時間復雜度O(25*88*log(state)+T*25*87*log(state))。

  加一個判重的剪枝,可以跑得飛快。

  map還可以用O(1)的,hash還可以動態維護。所以時間復雜度可以優化到O(88

+T*87)。

  而且每個狀態不是滿的8種轉移方式,實際的時間復雜度遠遠小於上限。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#define ll unsigned long long
using namespace std;
typedef char matr[6][6];
const int fx[8][2]={{1,2},{1,-2},{-1,2},{-1,-2},{2,1},{2,-1},{-2,1},{-2,-1}};
map <ll,int> mp;
matr c,d;
int cx,cy,dx,dy,ans,t;

inline int read()
{
    int n=0,f=1; char c=getchar();
    while (c<0 || c>9) {if(c==-)f=-1; c=getchar();}
    while (c>=0 && c<=9) {n=n*10+c-0; c=getchar();}
    return n*f;
}

ll geth(matr c,int x,int y)
{
    ll h=0;
    register int i,j;
    for (i=1;i<=5;++i)
        for (j=1;j<=5;++j) h=h*2+c[i][j]-0;
    h=h*5+x-1; h=h*5+y-1;
    return h;
}

bool check(int x,int y) {return x<1||x>5||y<1||y>5;}

void dfs1(int stp)
{
    ll h=geth(c,cx,cy);
    if (mp[h]&&mp[h]<=stp) return;
    mp[h]=stp;
    if (stp>8) return;
    register int i,ncx,ncy;
    for (i=0;i<8;++i)
    {
        ncx=cx+fx[i][0];
        ncy=cy+fx[i][1];
        if (check(ncx,ncy)) continue;
        swap(c[cx][cy],c[ncx][ncy]);
        swap(cx,ncx); swap(cy,ncy);
        dfs1(stp+1);
        swap(cx,ncx); swap(cy,ncy);
        swap(c[cx][cy],c[ncx][ncy]);
    }
}

void dfs2(int stp)
{
    ll h=geth(d,dx,dy);
    if (mp[h]) ans=min(ans,stp+mp[h]-2);
    if (stp>7) return;
    register int i,ndx,ndy;
    for (i=0;i<8;++i)
    {
        ndx=dx+fx[i][0];
        ndy=dy+fx[i][1];
        if (check(ndx,ndy)) continue;
        swap(d[dx][dy],d[ndx][ndy]);
        swap(dx,ndx); swap(dy,ndy);
        dfs2(stp+1);
        swap(dx,ndx); swap(dy,ndy);
        swap(d[dx][dy],d[ndx][ndy]);
    }
}

int main()
{
    register int i,j;
    t=read();
    for (i=1;i<=5;++i)
        for (j=1;j<=5;++j)
                 if (i<j||i==j&&i<=2) c[i][j]=1;
            else if (i>j||i==j&&i>=3) c[i][j]=0;
    cx=cy=3; dfs1(1);
    while (t--)
    {
        ans=16;
        for (i=1;i<=5;++i) scanf("%s",d[i]+1);
        for (i=1;i<=5;++i)
        {
            for (j=1;j<=5;++j)
                if (d[i][j]==*) {dx=i; dy=j; d[i][j]=0; break;}
            if (j<=5) break;
        }
        dfs2(1);
        printf("%d\n",ans==16?-1:ans);
    }
}

Last Word

  不過仔細想想玄學題目並不只是十幾年前的專利啊,比如FJOI的D2T2……

[BZOJ]1085 騎士精神(SCOI2005)