《关于拼图游戏可解性证明》
问题及设定
1、将1~nn个数随机生成一个的排列,任取第N个数作为空块(其目标值为N0),然后将这nn个数(包括空块)填入n阶方阵中,请问:这个拼图可以还原吗?
2、设去掉N0后,排列的逆序数为Inv,N在nn方阵中的行数为r0,即初始空块所在的行数(从上往下数)。N0在nn方阵中的行数为rt,即拼图完成时空块所在的行数。
3、举例:随机生成一个3阶方阵数字:361592784,随机拿掉第6个方块,这个方块(空块)在3阶方阵中位于第2行,即r0=2,而它本身的数字是2,在拼图完成时应位于第一行,所以rt=1。上述排列去掉2后的逆序数等于:0+0+2+1+0+1+1+5=10,即Inv=10,下面探讨任意n阶生成时,是否都可解。
关键性质
1、任意交换相邻的两个数,排列的逆序数的奇偶性发生改变。
2、上下移动一个块,相当于相邻交换的操作做了n-1次。
3、只有方块的上下移动才改变逆序数,左右移动不改变逆序数。
4、拼图成功时每个块按顺序排列,其逆序数为0,故:目标状态为偶排列。
可解性推导
当 n 为奇数:
每次上下移动不改变 Inv 的奇偶性(n-1为偶),所以无论空块移动多少次,Inv 的奇偶性始终保持不变。目标状态要求 Inv 为偶数,因此 Inv初始 必须为偶数。
当 n 为偶数:
每次上下移动会改变 Inv 的奇偶性(由关键性质2,n-1为奇)。空块需要 |r0-rt| 次上下移动,这会使 Inv 的奇偶性改变 |r0-rt| 次。最终要得到偶排列(逆序数 0),故初始奇偶性加上改变次数必须为偶,即:Inv 的奇偶性 + |r0-rt| 的奇偶性 = 偶。
由于 |r0-rt| 与 r0-rt 同奇偶,条件可写为:Inv + (r0 - rt) 为偶数。
结论
1、当n为奇数时:Inv必须为偶数,拼图才可解。
2、当n为偶数时,Inv+r0-rt必须为偶数,拼图才可解。




关键代码如下:
//初始化棋盘
private void MakeBox(string fn,int N) //产生盒子,N=3,5,8
{
mButton = new AButton[N * N];
int i,r,c;
int w;
Image img;
this.Controls.Clear(); //移除之前的所有控件
w = 600 / N;
try
{
img = Image.FromFile(fn);
for (i = 0; i <N*N; i++)
{
r = i / N; c = i % N;
mButton[i] = new AButton(i+1, N, CaptureImage(img, w, w, c * w, r * w));
this.Controls.Add(mButton[i]);
}
}
catch (Exception e)
{
if (MessageBox.Show(e.ToString() + "\n 错误! 是否继续(Y/N)?", "错误", MessageBoxButtons.YesNo) == DialogResult.No)
{
Application.Exit();
}
}
}
public void InitA(int n) //初始化数组 和 图片 最后一个块必须空掉。数组置为0
{
A = new int[n, n];
int[] b = new int[n*n]; //随机化的一维数组。
Random rand = new Random();
int i, j,k;
int r, c;
bool TF;
int Inv;
//以下数字表示了图片的索引号。也是在棋盘中的位置。
r = 0;c = 0;
//生成不重复的随机数 1~n*n
b[0] = rand.Next(n*n) + 1;
i = 1;
while (i<n*n)
{
do
{
k = rand.Next(n*n) + 1;
j = 0;TF = false;
while (j < i & b[j] != k) j++;
if (j == i) TF = true;
} while (!TF);
b[i] = k;
i = i + 1;
}
//随机去掉一个块,作为空块,但要先记住它 。2026
int RemoveN = rand.Next(n * n); // 0~n*n-1
for (i = 0; i < n; i++)
for (j = 0; j < n; j++)
{
A[i, j] = b[i * n + j ];
if (i * n + j == RemoveN) //把随机去掉的块置0,并记录空块的位置
{
RememberN = A[i, j]; // 1~n*n
A[i, j] = 0;
r = i;
c = j;
}
}
int r0 = n - r; //空块从下往上数的行数,从1开始数。
rt =n-(RememberN-1)/ n; //目标行数。2026
Inv= GetInverse(A); //计算逆序数
if (n % 2 == 1 )
{
if (Inv % 2 == 1) //n为奇数时,逆序数必须为偶数才能完成。否则不可能完成拼图。
{
this.Text =$"{n}阶,逆序数为奇数,不可能完成的拼图!";
}
else
{
this.Text = "拼图游戏";
}
}
else //n为偶数时,逆序数加上空块所在行数必须与目标行数同奇偶性才能完成。否则不可能完成拼图。
{
if ((Inv + r0-rt ) % 2 !=0)
{
this.Text =$"{n}阶,逆序数加上空块行数-目标行数 应为偶性,否则不可能完成拼图!";
}
else
{
this.Text = "拼图游戏";
}
}
//初始化AButton的位置,mButton的索引号从0到n*n-1,而A数组的值从1到n*n-1,0表示空块。
for (i = 0; i < n; i++)
for (j = 0; j < n; j++)
if (A[i, j] >0) mButton[A[i, j] - 1].Location=new Point( j * 600/n,i*600/n);
mButton[RememberN-1].Location = new Point(6000, 6000); //把空块甩得远远地!:)
TotalStep = 0;
}
public static int GetInverse(int[,] A)// 计算逆序数,先把二维数组转成一维数组,去掉0后计算逆序数。2026
{
int i, j, Inv;
Inv = 0;
int[] cc = A.Cast<int>().Where(x => x != 0).ToArray();
for (i = 0; i < cc.Length - 1; i++)
for (j = i + 1; j < cc.Length; j++)
if (cc[i] > cc[j]) Inv ++;
return Inv;
}
9100

被折叠的 条评论
为什么被折叠?



