十五数码问题及其泛化

博客原文地址:http://zhyu.me/acm/hdu-4021.html

题意:给出一个board,上面有24个位置,其中23个位置上放置了标有数字1~23的方块,一个为空位(用数字0表示),现在可以把空位与它旁边的方块交换,给出board的起始状态,问是否可以达到指定的状态。

思路:看起来很像著名的“八数码”问题,首先,针对八个特殊位置(死角),如果这里有空位就把它和相邻的位置交换,这样之后如果两个状态的对应死角上的数字不同,那么显然是不能达到指定状态的,因为无法把死角处的数字换出去。

搞定了死角后就只剩下4×4的board了,这就变成了八数码问题的拓展——15数码。首先想想八数码是如何判断有解的:首先把所有数字(不包括空位的0)写成一行,就得到了一个1~8的排列,考虑空位的交换情况:1.左右交换,2.上下交换。对于左右交换而言,是不会改变写出的排列的逆序数的;而对上下交换,相当于在排列中向前或向后跳了两个位置,那么要么两个数都比它大或小,这样逆序数加2或减2,要么两个数一个比它大一个比它小,这样逆序数不变,综上,对于八数码问题,操作不会改变逆序数的奇偶性,所以只有初始状态和指定状态的逆序数奇偶性相同就有解。

弄清楚了八数码,扩展起来就容易了,现在我们将其扩展到N维(即N*N的board,N*N-1数码问题)。

首先无论N的奇偶,左右交换不改变逆序数,N为奇数时:上下交换逆序数增加N-1或减少N-1或不变,因为N为奇数,所以逆序数奇偶性不变;而N为偶数时:上下交换一次奇偶性改变一次。

结论:N为奇数时,初始状态与指定状态逆序数奇偶性相同即有解;N为偶数时,先计算出从初始状态到指定状态,空位要移动的行数m,如果初始状态的逆序数加上m与指定状态的逆序数奇偶性相同,则有解。

好了,现在这道题就简单了,计算逆序数和空格要移动的行数即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

/*
 *  Memory 204K Time 31MS   Language C++
 *
 *  code by : zhyu
 */
#include <cstdio>
#include <cstring>
#include <algorithm>
#define abs(x) ((x)>0?(x):-(x))
 
using namespace std;
 
const int pos[]={0,1,2,7,16,21,22,23};
int f[24];
 
int a[24],b[24],c[16],d[16];
 
void init(void)
{
    f[0]=f[2]=3;
    f[1]=f[7]=6;
    f[16]=f[22]=17;
    f[21]=f[23]=20;
}
int main(void)
{
    init();
    int t;
    scanf("%d",&t);
    while(t--)
    {
        for(int i=0;i<24;i++)    scanf("%d",a+i);
        for(int i=0;i<24;i++)    scanf("%d",b+i);
        for(int i=0;i<8;i++)
        {
            if(a[pos[i]]==0)    swap(a[pos[i]],a[f[pos[i]]]);
            if(b[pos[i]]==0)    swap(b[pos[i]],b[f[pos[i]]]);
        }
        bool flag=0;
        for(int i=0;i<8;i++)
        {
            if(a[pos[i]]!=b[pos[i]])
            {
                flag=1;
                break;
            }
        }
        if(flag)
        {
            puts("Y");
            continue;
        }
        int num1=0,num2=0;
        for(int i=0,j;i<24;i++)
        {
            bool f1=0;
            for(j=0;j<8;j++) if(i==pos[j])   f1=1;
            if(f1)  continue;
            c[num1++]=a[i];
            d[num2++]=b[i];
        }
        int pos1=-1,pos2=-1;
        int cnt1=0,cnt2=0;
        for(int i=1;i<16;i++)
        {
            if(c[i]==0) pos1=i;
            else
            {
                for(int j=0;j<i;j++)
                    if(c[i]<c[j])
                        cnt1++;
            }
        }
        for(int i=1;i<16;i++)
        {
            if(d[i]==0) pos2=i;
            else
            {
                for(int j=0;j<i;j++)
                    if(d[i]<d[j])
                        cnt2++;
            }
        }
        int diff=abs(pos1/4-pos2/4);
        if(abs(diff+cnt1-cnt2)%2==0)    puts("N");
        else    puts("Y");
    }
    return 0;
}
posted @ 2012-10-24 18:21  wmx3ng  阅读(2587)  评论(0编辑  收藏  举报