2018AHU新生赛Panelatta与华容道题解
题目描述
Panelatta很喜欢玩益智游戏,今天,他又开始玩一个古老的游戏:华容道!但是Panelatta不喜欢原版的华容道,所以他对此做了一些修改。给定一个由 n2n^{2}n
2
( n 为奇数)个方格组成的 n⋅n 方阵,在 n⋅n−1 个方格中填入数字 1 ~ n⋅n−1 ,并将剩下的一个方格留空。在游戏过程中,可以将空置的方格与其上下左右方向的任何一个数字交换。Panelatta将这个游戏称为 n 阶华容道。
现在给你两个 n 阶华容道游戏的局面,请判断是否存在一种移动空置方格的方法,使得从其中一个局面可以变换成另一个局面?
输入格式
多组数据,对于每组数据:
第1行一个奇数 n ,(n < 500)
接下来 n 行,每行 n 个整数,表示第一个局面。
接下来 n 行,每行 n 个整数,表示第二个局面。
每个局面中用 0 表示空置方格,并保证每个局面都恰好用数字 0 ~ n⋅n−1填充满。
输出格式
对于每组数据,若两个局面可达,输出TAK,否则输出NIE。
样例
输入样例
3
1 2 3
0 4 6
7 5 8
1 2 3
4 5 6
7 8 0
1
0
0
输出样例
TAK
TAK
注:xls出的这个题没点相关知识的话太难了,不愧是AHU全能王,出题就是有水平 ?
下面先介绍这道题所涉及到的相关知识点:
奇排列
下面是百度百科上对奇排列 先自行阅读后再看下面的解释
https://baike.baidu.com/item/奇排列/18881587?fr=aladdin
看完百度百科对奇排列的解释后 不难知道以下结论:
定理1 : 对换改变排列的奇偶性。(经过一次对换,奇排列变成偶排列,偶排列变成奇排列。
例如:1 2 3 4 5 6是逆序对为0 所以为偶排列 若调换其中任意两个位置的数 比如1 5 3 4 2 6 就变成偶排列
定理2 :任意一个n级排列与排列 都可以经过一系列对换互变,并且所作对换的个数与这个排列有相同的奇偶性。
定理2结合定理1也不难理解,偶排列换一次变成奇排列,再变化一次就变成了偶排列,以此类推得知:互变的次数的奇偶性与排列的奇偶性保持一致。
那么华容道是怎样具有排列的性质呢?
首先理解华容道不一定总是有解的,比如网上很火的一道数字华容道题
具体可以看下这个有关华容道里群论的视频:华容道群论视频
这种数字华容道是无解的,而原因就是因为它是一种奇排列,而他要变成的1 2 3 4…… 14 15是一种偶排列,我们不难想到奇排列与偶排列在数字华容道中不能互相转化。
了解了上面的知识再来看这道题 给出两个数字华容道 问第一个能否转换成第二个,换个思路想,问题可以转化为第一个第二个是否都是同一种排列(比如都是奇排列和偶排列)。转化为这个问题后,我们又要想到如何判断数字华容道的排列的奇偶性,那不妨找一个标准排列来与这两个数字华容道作比较,看看这两个数字华容道要互换几次才能变成我们所规定的那个标准排列,若两种变化次数为奇数或偶数,说明这两种排列具有相同的奇偶性,故可以相互转化,输出TAK。
不妨令标准排列为1 2 3 4 5 6 7 ……
此时问题就转化为了一个排列最少交换几次才能变成1 2 3 4 5 6 7……这种升序排序。
由于数据为500*500=250000 数据很大 冒泡排序等方法很容易被卡超时
下面介绍一种复杂度o(n)的方法来处理这种问题:
创建一个数组记录一组数 然后再创建一个数组来记录数所对应的位数 比如第一组数 1 2 3 4 6 7 5 8 第一个数组叫a[1]=1 a[2]=2…a[5]=6 a[6]=7 a[7]=5 然另一个数组arem[1]=1 arem[2]=2 arem[6]=5 arem[7]=6 arem[5]=7 然后遍历 如果a[i] ! =i 就让arem[i]查到i值在第几位 然后让a[i]与a[arem[i]]交换数值 同时更新arem数组
注:更新arem数组的作用何在?
如果交换完位置后还用原来的数值记录位置就会在后面调取某个数值的位置时产生错误。
ac代码
// An highlighted block
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
int a[250000],arem[250000];
int b[250000],brem[250000];
int main()
{
int t=10;
while(t--)
{
int n,cnta=0,cntb=0,tep;
scanf("%d",&n);
if(n==0){continue;};
if(n==1)
{
scanf("%d",&n);
scanf("%d",&n);
printf("TAK\n");
continue;
}
for(int i=1;i<=n*n-1;i++)
{
scanf("%d",&a[i]);
if(a[i]==0){i=i-1;continue;}
arem[a[i]]=i;
}
for(int i=1;i<=n*n-1;i++)
{
scanf("%d",&b[i]);
if(b[i]==0){i=i-1;continue;}
brem[b[i]]=i;
}
int m,q;
for(int i=1;i<=n*n-1;i++)
{
if(a[i]!=i){m=arem[i];q=a[i];tep=a[arem[i]];a[arem[i]]=a[i];a[i]=tep;cnta++;arem[q]=m;}
if(b[i]!=i){m=brem[i];q=b[i];tep=b[brem[i]];b[brem[i]]=b[i];b[i]=tep;cntb++;brem[q]=m;}
}
if(cnta%2==cntb%2){printf("TAK\n");}
else{printf("NIE\n");}
}
return 0;
}
幸运的是这道题内存限制没什么要求,但是我们可以进一步优化下内存,我们可以让a和arem数组输入完成后直接进入运算cnta的奇偶性,然后再把原来的b和brem直接输入到a和arem,这样就省去了b和brem所占有的内存。
下面是改进版本的代码:
// An highlighted block
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
int a[250000],arem[250000];
int main()
{
//freopen("1.in","r",stdin);
int t=10;
while(t--)
{
int n,cnta=0,cntb=0,tep;
int m,q;
scanf("%d",&n);
if(n==0){continue;};
if(n==1)
{
scanf("%d",&n);
scanf("%d",&n);
printf("TAK\n");
continue;
}
for(int i=1;i<=n*n-1;i++)
{
scanf("%d",&a[i]);
if(a[i]==0){i=i-1;continue;}
arem[a[i]]=i;
}
for(int i=1;i<=n*n-1;i++)
{
if(a[i]!=i){m=arem[i];q=a[i];tep=a[arem[i]];a[arem[i]]=a[i];a[i]=tep;cnta++;arem[q]=m;}
}
for(int i=1;i<=n*n-1;i++)
{
scanf("%d",&a[i]);
if(a[i]==0){i=i-1;continue;}
arem[a[i]]=i;
}
for(int i=1;i<=n*n-1;i++)
{
if(a[i]!=i){m=arem[i];q=a[i];tep=a[arem[i]];a[arem[i]]=a[i];a[i]=tep;cntb++;arem[q]=m;}
}
if(cnta%2==cntb%2){printf("TAK\n");}
else{printf("NIE\n");}
}
return 0;
}
内存少了一半左右,且排序算法是o(n)的,现在应该是优化的很好的代码啦。
下面再贴一种出题人Xls滴方法。
xls滴排序是o(nlogn)的方法。
// An highlighted block
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=5e5+5;
long long ans;
int a[maxn];
int r[maxn];
void msort(int s,int t){
if(s==t)return;
int mid=(s+t)/2;
msort(s,mid);
msort(mid+1,t);
int i=s,j=mid+1,k=s;
while(i<=mid&&j<=t){
if(a[i]<=a[j]){
r[k]=a[i];k++;i++;
}
else{
r[k]=a[j];
k++;j++;
ans+=mid-i+1;
}
}
while(i<=mid){
r[k]=a[i];k++;i++;
}
while(j<=t){
r[k]=a[j];k++;j++;
}
for(int i=s;i<=t;i++)
a[i]=r[i];
}
int main(){
int n;
int p;
int val;
while(~scanf("%d",&n)){
p=1;
for(int i=1;i<=n*n;i++){
scanf("%d",&val);
if(val!=0){
a[p]=val;
p++;
}
}
ans=0;
msort(1,p);
long long t=ans;
p=1;
for(int i=1;i<=n*n;i++){
scanf("%d",&val);
if(val!=0){
a[p]=val;
p++;
}
}
ans=0;
msort(1,p);
if(t%2==ans%2)printf("TAK\n");
else printf("NIE\n");
}
return 0;
}