题目描述:
你一定玩过八数码游戏,它实际上是在一个3×3的网格中进行的,1个空格和1~8这8个数字恰好不重不漏地分布在这3×3的网格中。
例如:
5 2 8
1 3 _
4 6 7
在游戏过程中,可以把空格与其上、下、左、右四个方向之一的数字交换(如果存在)。
例如在上例中,空格可与左、上、下面的数字交换,分别变成:
5 2 8 5 2 _ 5 2 8
1 _ 3 1 3 8 1 3 7
4 6 7 4 6 7 4 6 _
奇数码游戏是它的一个扩展,在一个n×n的网格中进行,其中n为奇数,1个空格和1~n2−1这n2−1个数恰好不重不漏地分布在n×n
的网格中。空格移动的规则与八数码游戏相同,实际上,八数码就是一个n=3的奇数码游戏。现在给定两个奇数码游戏的局面,请判断是否存在一种移动空格的方式,使得其中一个局面可以变化到另一个局面。
输入格式
多组数据,对于每组数据:第1行输入一个整数n,n为奇数。接下来n行每行n个整数,表示第一个局面。再接下来n行每行n个整数,表示第二个局面。局面中每个整数都是0~n2−1之一,其中用0代表空格,其余数值与奇数码游戏中的意义相同,保证这些整数的分布不重不漏。
输出格式
对于每组数据,若两个局面可达,输出TAK,否则输出NIE。
数据范围
1≤n<500
输入样例:
3
1 2 3
0 4 6
7 5 8
1 2 3
4 5 6
7 8 0
1
0
0
输出样例:
TAK
TAK
分析:
解决本题的关键在于一个结论,即:奇数码游戏两个局面可达,当且仅当这两个局面按行排成一个一个序列的逆序对数目的奇偶性相同。显而易见,当空格在当前行左右移动,并不会改变其逆序对数目。对于第一个局面1 2 3 0 4 6 7 5 8,0若上移得到0 2 3 1 4 6 7 5 8 0,相当于1向右跳了两个位置,逆序对奇偶性必然不变。更一般的,对于n数码问题,空格上(下)移,相当于一个数向右(左)跳了n-1个位置,而n是奇数,所以不会影响逆序对的数目。所以不论空格如何移动,奇数码局面的逆序对数目的奇偶性不变。
于是可以转化为上一题,调用两次归并函数求得两个局面逆序对的数目,再判断奇偶性是否相同即可。需要注意的是:
第一,对于0的处理,我们必须在读入时跳过0,0不参与序列。
第二,复杂度,直接调用上一题的函数,发现超时了。上一题的merge函数辅助向量是定义在函数体内的,也就是局部变量,这样做的好处是每次递归会自动创建辅助向量,调用完自动销毁。但是正是这个局部的向量,会消耗大量的时间。本题是多组数据一起评测,所以,相当于多组奇数码问题处理时间的总和要比较低。每一个局部变量创建销毁的时间都可能被乘以十万乃至上亿次,何况还是向量。于是将辅助向量定义为全局的数组即可ac,节省了相当多的时间。
#include <iostream>
#include <vector>
using namespace std;
typedef long long ll;
ll ans = 0;
int t[250010];
void merge(vector<int> &v,int l,int r){
if(l >= r) return;
int mid = l + r >> 1;
merge(v,l,mid);
merge(v,mid+1,r);
int i = l,j = mid + 1,p = l;
while(i <= mid && j <= r){
if(v[i] <= v[j] ) t[p++] = v[i++];
else{
t[p++] = v[j++];
ans += (mid - i + 1);
}
}
while(i <= mid) t[p++] = v[i++];
while(j <= r) t[p++] = v[j++];
for(i = l;i <= r;i++) v[i] = t[i];
}
int main(){
int n;
while(cin>>n){
int s = n * n - 1;
vector<int>a(s),b(s);
int k;
for(int i = 0,j = 0;i <= s;i++){
cin>>k;
if(k) a[j++] = k;
}
for(int i = 0,j = 0;i <= s;i++){
cin>>k;
if(k) b[j++] = k;
}
ans = 0;
merge(a,0,s-1);
ll f;
f = ans,ans = 0;
merge(b,0,s-1);
ans += f;
if(ans % 2) cout<<"NIE"<<endl;
else cout<<"TAK"<<endl;
}
return 0;
}