题目链接:
hdoj 3600 Simple Puzzle
分析:
本题是八数码解存在性问题的扩展。至于解的存在的问题转换为逆序对的奇偶性问题(具体证明略)。起始状态的排列的逆序对若与目标状态的逆序对奇偶性相同则可以存在解。求逆序对的方法可以用线段树或者归并排序。而逆序对的考虑除了排列本身(去掉空格0)还有空格0也会影响其奇偶性,具体影响方式为,若数码是奇数列则直接判断始末状态逆序对的奇偶性是否相等。
若是偶数列,则判断 末状态逆序对 与 始状态逆序对+空格的垂直偏移量是否奇偶性相同。(证明再次略过,可参考其他博客)。
代码:
#include<stdio.h>
#include<iostream>
#include<string>
#include<vector>
#include<map>
#include<queue>
#include<set>
#include<math.h>
#include<string.h>
#include<algorithm>
using namespace std;
int a[90000],t[90000],ans;
void Ms(int l,int m,int r)
{
int i=l;
int j=m+1;
int k=l;
while(i<=m&&j<=r){
if(a[i]>a[j]){
ans+=m-i+1;
/*将两个有序的排列合并时
前面排列的a[i]如果大于后面排列种的元素a[j],则说明对于a[j]来说,前面有m-i+1个比它大的数。
有人会担心排序过程中改变了排列导致逆序数会改变,
仔细想想,二分的归并排序在这里才开始改变元素位置,
但a[j]前面到底有多少个更大的数呢(排列后半段在a[j]前的都没它大),而排列的前半段(有序)的排列顺序不会有影响,
对于每个元素都是如此。*/
t[k++]=a[j++];
}
else
t[k++]=a[i++];
}
while(i<=m)t[k++]=a[i++];
while(j<=r)t[k++]=a[j++];
for(i=l;i<=r;i++)
a[i]=t[i];
}
void Merge(int l,int r)
{
if(l<r){
int m=(l+r)/2;
Merge(l,m);
Merge(m+1,r);
Ms(l,m,r);
}
}
int main()
{
freopen("hdoj3600.txt","r",stdin);
int n,num,r,c,k=0;
while(cin>>n){
if(n==0)break;
k=0;
ans=0;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++){
cin>>num;
if(num==0)
r=i;
else
a[k++]=num;
}
Merge(0,k-1);
if(n%2==1){//奇数列且目标与起始状态的奇偶性相同
if(ans%2==0)
cout<<"YES"<<endl;
else
cout<<"NO"<<endl;
}
else {
if((ans+n-1-r)%2==0)//偶数列但是空格的始末上下距离差与逆序数的和为偶数
cout<<"YES"<<endl;
else
cout<<"NO"<<endl;
}
}
return 0;
}