背景
这题其实是 奇数码问题, \(AcWing108/CH0503\) 的原版,\(POJ\) \(Monthly\) \(2006.7.30\) \(H\)题, \(POJ2893\)。
题意
给定 \(m \times n\) ( \(m,n\) 为整数, \(2 \leqslant m,n \leqslant 999\) )的棋盘,上面有 \(m \times n-1\) 个数和一个空格。给出一个局面,每次仅允许交换空格和其四周相邻的一个数(如果有的话),问是否有办法将这个局面移动为目标局面(空格在最后一行最后一列,其余格 \((i,j)\) 上的数为 \((i-1)*m+j\) )。
解法
将棋盘上的数一行一行取出排成一列(空格不取),可以得到一个序列。
思考操作的数学意义。左右交换空格和数字对该序列的逆序对数毫无影响。而上下交换空格和数字则会交换序列中 \(n-1\) 个数的位置。又由于 \(n\) 为整数,所以 \(n-1\) 需要分类讨论。
设交换前有 \(a\) 个数小于交换的数 \(x\) ,有 \(b\) 个数大于 \(x\) ( \(a+b=n-1\) , \(a \not= b\) )。则交换前交换的数的逆序对数为 \(a\) ,交换后交换的数的逆序对数为 \(b\) 。若 \(a < b\) ,则逆序对数增加了 \(b-a\) ,否则逆序对数减少了 \(a-b\) 。
\(n\) 为奇数时, \(n-1\) 为偶数,则交换前后逆序对数的变化一定为偶数。两个局面能够从一个操作成另一个当且仅当两个局面对应的序列的逆序对数奇偶性相同,否则一定不能转化。奇偶性相同为充要条件,其必要性十分容易说明,但充分性 \(\cdots \cdots\) 具体证明过程较长,这里不再展开。又因为目标局面的逆序对数为 \(0\) ,所以原序列的逆序对数为奇数时,不能达成目标,否则可以。
而 \(n\) 为偶数时, \(n-1\) 为奇数,每次交换前后逆序对数的变化一定为奇数。算出上下交换的次数(原局面和完美局面空格相差的行数) \(p\) 。设原序列的逆序对数为 \(q\) ,当 \(p\) 、 \(q\) 奇偶性相同时,可以达成目标,否则不能。
然后对原局面归并排序求逆序对数即可。时间复杂度为 \(O(n^2)\) 。(瓶颈在读入)树状数组求逆序对的时间复杂度是 \(O((n+m)\) \(log\) \(n)\) ( \(m\) 为数值范围大小),本题中 \(m = 998001\) ,故也可以通过本题。喜欢写哪个就写哪个呗 \(QAQ\)
\(trick\)
\(1.\) 先把情况分析讨论清楚再开始写 \(code\) 。
\(2.\) 目标局面显然逆序对为 \(0\) ,无需建序列求逆序对。
细节
\(1.\) 题面的网格为 \(m \times n\) 而不是 \(n \times m\) 。(好傻逼的错误啊)
代码
我也不知道怎么肥四,反正这篇 \(blog\) 的代码框就是合不了,然后一堆 \(bug\) 神马的 \(\cdots \cdots\) ,所以就不收起来了,泥萌自己看吧 \(QwQ\)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
inline int read()
{
int ret=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
ret=(ret<<1)+(ret<<3)+ch-'0';
ch=getchar();
}
return ret*f;
}
int n,m,sp,tmp,k,rr,a[998005],b[998005];
long long ans;
void msort(int l,int r,int mid)
{
if(l==r)
return;
msort(l,mid,(l+mid)>>1);
msort(mid+1,r,(mid+1+r)>>1);
int i=l,j=mid+1;
for(register int k=l;k<=r;k++)
{
if(j>r||(i<=mid&&a[i]<a[j]))
b[k]=a[i++];
else
{
ans+=mid-i+1;
b[k]=a[j++];
}
}
for(register int k=l;k<=r;k++)
a[k]=b[k];
}
int main()
{
while(1)
{
m=read();
n=read();
if((!m)&&(!n))
break;
k=0;
for(register int i=1;i<=m;i++)
{
for(register int j=1;j<=n;j++)
{
tmp=read();
if(tmp)
a[++k]=tmp;
else
sp=i;
}
}
sp=m-sp;
ans=0;
rr=m*n-1;
msort(1,rr,(1+rr)>>1);
if(n&1)
{
if(ans&1)
puts("NO");
else
puts("YES");
}
else
{
if((sp+ans)&1)
puts("NO");
else
puts("YES");
}
}
return 0;
}