108.奇数码问题

原题链接:108. 奇数码问题



解题思路

奇数码问题两个局面可以达成,当且仅当两个局面下网格中的数以此写成1行 n*n-1 个元素的序列后(不考虑空格),逆序对个数的奇偶性相同。例如题目描述中的第一个局面可以写成[5 2 8 1 3 4 6 7]。该结论的必要性很容易证明:空格左右移动时,写数交换了位置,因为n-1是偶数,所以逆序对数的变化也只能是偶数。

上面的结论还可以扩展到n为偶数的情况,此时两个局面可达,当且仅当两个局面对应网格写成序列后,“逆序对数之差”和“两个局面下空格所在行数之差”奇偶性相同。事实上,在n*m网格中(n,m>=2)也服从上述的两个结论之一(根据列数奇偶性分情况讨论)。

总而言之,n*m数码问题的有解性判定,可以转化为归并问题排序就逆序对来解决

样例代码

#include <bits/stdc++.h>
using namespace std;
const int N=510;
int n,m,a[N*N],b[N*N],c[N*N],i,j,k;
long long cnt;
void merge(int a[],int l,int r)
{
    if (r-l<1)
        return ;
    int mid=(l+r)>>1;
    merge(a,l,mid);
    merge(a,mid+1,r);
    int i=l,j=mid+1;
    for (int k=l; k<=r; k++)
    {
        if (j>r || i<=mid && a[i]<=a[j])
            b[k]=a[i++];
        else
        {
            cnt+=mid-i+1;
            b[k]=a[j++];
        }
    }
    for (int k=l; k<=r; k++)
        a[k]=b[k];
}
signed main()
{
    ios::sync_with_stdio(false);
//  freopen("stdin.in","r",stdin);
//  freopen("stdout.out","w",stdout);
    while(cin>>n)
    {
        int ok=0,x;
        for (i=1; i<=n*n; i++)
        {
            cin>>x;
            if (x==0)
                ok=1;
            else
                a[i-ok]=x;
        }
        ok=0;
        for (i=1; i<=n*n; i++)
        {
            cin>>x;
            if (x==0)
                ok=1;
            else
                c[i-ok]=x;
        }
        cnt=0;
        memset(b,0,sizeof(b));
        merge(a,1,n*n);
        long long ans=cnt;
        memset(b,0,sizeof(b));
        cnt=0;
        merge(c,1,n*n);
        if ((ans&1)==(cnt&1))
            puts("TAK");
        else
            puts("NIE");
    }
    return 0;
}
posted @ 2021-01-14 19:01  hnkjdx_react  阅读(135)  评论(0编辑  收藏  举报