N数码问题的证明

前言

最近做到了两道N数码问题,才让我对这个问题产生了重视。这次审视上一次得出的证明,却发现有一个地方模棱两可。这个结论我也是听大家说才想到的,然而我发现大多数人并不能严谨的证明,所以我想再次谈一谈这个问题。
有一位大佬就这个问题写得非常好,也给大家多一份参考资料,博客在这

什么是N数码问题

相信你一定玩过这样的游戏,有一个边长为n的矩阵,里面随机分布着1~ n 2 n^2 n2-1的数,还有一个是空格,每次空格可以和相邻的数进行交换。你的目标是把这些数最终排成有序,空格在右下角。
没玩过也没关系,你可以使用鼠标右键-小工具-图片拼图版,自己来体验一下。就是下图这种东西。
这里写图片描述
最终状态是这样的:
这里写图片描述
最基础的N数码问题就是给你一个矩阵,让你判断是否能移到最终状态。
稍微升级一下,就给你两个矩阵,问你是否能从一个矩阵,通过移动,到达另一个矩阵。

关于N数码问题的思路

也不知道是谁发现了可以用逆序对来做。把上面的乱序图按行展开成链,空格看做0,得到这样一个序列:
7 6 8 15 4 14 3 1 11 2 9 12 13 0 5 10
先把矩阵化为一个序列,我们再进行下面的讨论。
以下两种理论是用两种方法来解决这一道题(虽然大同小异)。

步数理论

结论:先对上面的序列(包括0)求逆序对,再求出0到右下角的曼哈顿距离(就是0要走几步才能走到右下角),若逆序对数与0要走的步数奇偶性相同,则可以移到最终状态,若奇偶性不同,则不能移到最终状态,但能互相到达。
我们可以正着思考这个问题,也可以倒着来。从乱序走到有序,也可以认为是有序走到乱序。于是有:
理论1:0从右下角出发,走奇数步,逆序对为奇数,走偶数步,逆序对为偶数。(若最终能走到,则0在右下角时(0走0步),逆序对必为偶数)
理论2:0在右下角,其他顺序任意的,逆序对为偶数的情况必能全部走到最终状态;0在右下角,其他顺序任意的,逆序对为奇数的情况不能走到最终状态,但都能到达某种状态。

只要证明了以上两个理论,那么结论必然成立(若乱序的逆序对数与0走的步数奇偶性相同,则可以先把0移到右下角,根据理论1,逆序对数就变成偶数了,再根据理论2,这样必然能变成最终状态;反之,乱序的逆序对与0走的步数奇偶性不同,也可以把0移到右下角,根据理论1,逆序对数为奇数,再根据理论2,它们都能到达某种状态,也就能互相到达了)。接下来开始证明。

理论1的证明:证明这个结论,只需要证明,0每走一步,逆序对改变了奇数个就行了。0左右走一步,逆序对就改变了一个,显然成立。0上下走一步,情况就比较麻烦了。假设0是向上走的,0上面那个数为a,因为原矩阵的边长为n,所以a与0之间有n-1个数。数列大概长这样:
……a……0……
假设中间这n-1个数中有x个数比a小,这n-1个数自身产生了y个逆序对。0是比所有数都小的。a与后面的n个数(包括0)产生了x+1个逆序对,0与前面的n-1个数产生了n-1个逆序对,中间这n-1个数自身又产生了y个逆序对,那么这n+1个数产生的逆序对总数是x+y+n。
交换后,数列大概长这样:
……0……a…
这n+1个数产生的逆序对数为n-1-x+y,那么交换前后改变了 ∣ x + y + n − ( n − 1 − x + y ) ∣ = ∣ 2 ∗ x − 1 ∣ |x+y+n-(n-1-x+y)|=|2*x-1| x+y+n(n1x+y)=2x1个逆序对。因为 |2*x-1|是个奇数,所以0向上移会改变奇数个逆序对。向下移同理。
至此,已经证明了无论0向上下左右哪个方向走一步,都会改变奇数个逆序对。理论1得证。

理论2的证明:前提是0在右下角。显然,我们有某种方法,使得1能回到原位,2也可以。这样一直到n-1,前n-2个数都能轻易归位(不信,你自己试一试)。而如果n-1归位的话,n将很难归位,所以可以把n放在n-1的位置上,再把n-1放在n的下面,像这样(a,b,c,d,x,y可以是任何数):
1 2 3……n-2 n x
a b c……d n-1 y
……………………
……………………
然后只要把0移到y的位置,0再分别和x,n,n-1交换位置,n-1和n就归位了(也就是至少有一个边长为2的正方形才可以)。 经过上面的操作,第一行是能轻松归位的。同理,第一列也能轻松归位。那第二行,第二列也都可以。一直到只剩下右下角的四个格子,刚才的策略才会失效。然后再把0移到右下角的那一格。经过这么多操作,0的起始位置和最终位置没变过,根据理论1,逆序对的奇偶性也没变,所以这四个数的逆序对数等于原来0在右下角时的乱序的逆序对数。计算可知,除了0以外,右下角边长为2的正方形剩下的三个数分别为 n 2 − 1 , n 2 − n , n 2 − n − 1 n^2-1,n^2-n,n^2-n-1 n21,n2n,n2n1,我们不妨把这三个数离散化,按大小依次离散成3,2,1。因为只有三个数(0的位置已经确定了,在最后一个),于是可以枚举。以下把它们的排列和逆序对数一个一个列举出来:
1 2 3 0
1 3 2 1
2 1 3 1
2 3 1 2
3 1 2 2
3 2 1 3
以2 3 1为例,它有2个逆序对,写成矩阵形式:
2 3
1 0
只要0先后和3,2,1,3交换位置,就会变成:
1 2
3 0
这就是我们想要的最终结果。对其它几个进行枚举,得到:逆序对数为偶数的可以移成上面的最终结果,而逆序对数为奇数的都不行,而且都能移到下面这种状态,无一例外。
2 1
3 0
既然原来0在右下角,其它为乱序时的逆序对数等于最后这四个数的逆序对数,而只要这四个数的逆序对数为偶数,就一定能移成最终状态,那么只要原来逆序对数为偶数(0在右下角),就一定能移到最终状态。奇数的话都能移到上面的状态,所以互相之间必能互相到达。证毕。

用一句话总结一下吧,只要能走到,就必然得满足要求(理论1);只要满足要求,就必然能走到(理论2)。
于是有了下面的代码:

#include<iostream>
#include<cstdio>
using namespace std;
int k,n,a[1000005],ans;
void merge_sort(int l,int r)
{
	if (l>=r) return;
	int m=(l+r)/2;
	merge_sort(l,m);
	merge_sort(m+1,r);
	int b[r-l+5];
	int i=l,j=m+1,t=0;
	while (i<=m&&j<=r)
	{
		if (a[i]<=a[j]) b[t++]=a[i++];
		else
		{
			b[t++]=a[j++];
			ans+=m-i+1;
		}
	}
	while (i<=m) b[t++]=a[i++];
	while (j<=r) b[t++]=a[j++];
	for (int i=l;i<=r;i++) a[i]=b[i-l];
}
int main()
{
	cin>>k;//有k组数据 
	int x;
	for (int l=1;l<=k;l++)
	{
		cin>>n;//n为矩形边长 
		ans=0;
		for (int i=1;i<=n;i++)
		for (int j=1;j<=n;j++)
		{
			cin>>a[(i-1)*n+j];//把矩阵转化成链 
			if (a[(i-1)*n+j]==0) x=n-i+n-j;//x为0到右下角的曼哈顿距离 
		}
		merge_sort(1,n*n);//归并排序求逆序对 
		if (ans%2==x%2) cout<<"Yes\n";//能走到最终状态 
		else cout<<"No\n";//不能走到最终状态  
	}
}

奇数理论

这种理论主要解决n为奇数的情况。(我做到的题都有说明n为奇数)其实这种理论和步数理论差不多,只是在实现方式上略有不同。
要用这种方法,先得把展开为链的序列中的0去掉(算逆序对时不考虑0)。
结论:若经过上述处理的逆序对数为偶数,则可以移到,否则不行。
理论1:若能变成最终状态,则逆序对数必为偶数。
理论2:若逆序对数为偶数,则必然能到最终状态;若逆序对数为奇数,则两者必能互相到达。

理论1的证明:证明这个,只要证明,从最终状态开始走,走到的状态逆序对必为偶数。首先,考虑0左右移动,不改变逆序对数(因为不考虑0产生的逆序对)。其次上下移动的话,假设0向上移动(向下同理),与a交换位置,假设0与a中间这n-1个数中有x个数比a小,则有n-1-x个数比a大。那么开始的逆序对数为x,交换后的逆序对数为n-1-x,逆序对改变了|n-1-2*x|个。因为n为奇数,所以逆序对改变了偶数个。至此,证明了0向上下左右每个方向移动,改变的逆序对数均为偶数,命题得证。

理论2的证明:基本上和步数理论的证明差不多。先把第一列,第一行等归位,直到最后 2 ∗ 3 2*3 23(2行3列)的格子。为什么不是 2 ∗ 2 2*2 22呢?因为奇数理论要求列数为奇数。现在,不管怎么移动,逆序对数的奇偶性还是没变。此时,把剩下的几个数分别视为1,2,3,4,5,0。然后再把1和4归位。如何归位1和4呢?有必要详细地说明。首先,把1移到4的位置上,如下:
x x x
1 x x
肯定能做到吧。再把4移到1的右边,如下:
x x x
1 4 x
也肯定能做到吧。再把0移到1的上面。
0 x x
1 4 x
更加肯定能做到了吧。好戏要开始了。0先和1交换位置,再和4交换位置。
1 x x
4 0 x
最后把0归位。
1 x x
4 x 0
为什么要讲这么详细呢,因为肯定能移成这种状态,而且逆序对奇偶性还不变。现在乱序的就只剩2,3,5三个数了。再把这三个数视为1,2,3。因为三个数全排列只有6种,可以枚举是否能移到最终状态。通过枚举发现(这里就不列出了),逆序对为偶数的,都能移动到最终状态;逆序对为奇数的,都能移到这种状态:
2 1
3 0
所以开始逆序对为偶数的都能移到最终状态;开始逆序对为奇数的都能移到某种状态,所以能互相到达。命题得证。

下面是判断一个矩阵是否能移到最终状态的程序(保证n为奇数)。

#include<iostream>
#include<cstdio>
using namespace std;
int k,n,a[1000005],ans;
void merge_sort(int l,int r)
{
	if (l>=r) return;
	int m=(l+r)/2;
	merge_sort(l,m);
	merge_sort(m+1,r);
	int b[r-l+5];
	int i=l,j=m+1,t=0;
	while (i<=m&&j<=r)
	{
		if (a[i]<=a[j]) b[t++]=a[i++];
		else
		{
			b[t++]=a[j++];
			ans+=m-i+1;
		}
	}
	while (i<=m) b[t++]=a[i++];
	while (j<=r) b[t++]=a[j++];
	for (int i=l;i<=r;i++) a[i]=b[i-l];
}
int main()
{
	cin>>k;//有k组数据 
	int x;
	for (int l=1;l<=k;l++)
	{
		cin>>n;//n为矩形边长 
		ans=0;
		int flag=0;
		for (int i=1;i<=n;i++)
		for (int j=1;j<=n;j++)
		{
			scanf("%d",&a[(i-1)*n+j+flag]);
			if (flag==0&&a[(i-1)*n+j]==0) flag=-1;
		}
		merge_sort(1,n*n);//归并排序求逆序对 
		if (ans%2==0) cout<<"Yes\n";//能走到最终状态 
		else cout<<"No\n";//不能走到最终状态  
	}
}

总结

写完之后,感觉有些该详细的没有详细,该简要的又写了一大堆废话。还是我的语言表达能力太差了吧。不过这个问题我也研究了好久才有了呈现在大家面前的证明。
爱因斯坦说过:“追求真理比占有真理更加难能可贵。”
我这篇博客只是想抛砖引玉,让大家用更加简洁,更加美妙的方法来证明这个问题,而不是曾经听说过。
或许对于信息竞赛来说,猜到了结论比给出一个完整的证明要简单,也更实在,但深入思考证明才是这道题全部的价值。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值