Atcoder【AGC006E】 Rotate 3x3

Description

XFZ在北京一环内有一套房。

XFZ房子的地砖呈网格状分布,是一个3∗N的网格。XFZ在买下这套房时,每个地砖上有一个数字,位置为(i,j)的地砖上的数字恰好为i+3(j−1)。

在这里插入图片描述
N=5时XFZ家的俯视图

XFZ的房子特别高级,地底暗藏转轴机关。每次转轴可以顶起一片3x3的地砖,将其旋转180°,再放下地砖。

在这里插入图片描述

一个转轴作业的例子(蓝色区域为旋转完成之后的区域)

XFZ决定要让地砖有美感。他希望他能使用他的高级转轴达到一个目的:对于位置为(i,j)的地砖,其数字恰好为ai,j。其中a是一个XFZ指定的3∗N的目标数组。


Input

    第一行一个正整数N(5≤N≤105)

    接下来3行,每行N列,描述ai,j(1≤ai,j≤3N)

    保证所有的ai,j互不相同。

Output

    如果XFZ的目标能够实现,输出“Yes”,否则输出“No”。二者皆不包含引号。

Solution

我一开始以为不能保持其他不变的情况下旋转上下…
首先上下一定与中间绑定在一起,奇偶行互不影响(除了造成上下翻转).

设列A,B,C,D,E分别为列a,b,c,d,e的逆序列:
a b c d e
C B A d e
C B E D a
e b c D a
e b A d C
a B E d C
a B c D e
a d C b e
c D A b e
c B a d e
A b C d e
然后你发现是可以对相差1的两列同时进行上下翻转的.
于是就变成了一个上下颠倒个数的奇偶性问题.

因为只考虑奇偶,所以不管你怎么做,只要先排好列,再考虑上下颠倒列数的奇偶性.

那么有一个很暴力的想法:
我们尝试将给定序列转回原序列.
设给定的矩阵中第i列第二行的值为x,则to[i]=x,即第i位第二行现在的数是x。
记录奇、偶数列中上下颠倒列数的奇偶性为t[0]、t[1]。

枚举1到n,若i ≠ \neq =to[i],不断swap(to[i],to[to[i]])(交换i行与to[i]行的数)
每次swap使

t[i&1^1]^=1

(即奇偶不同的那一行上下颠倒行数的奇偶性变化)
最后检查t[0]和t[1]是否都为0即可。

这样做不仅是对的,还是O(n)的。
我们发现to[i]没有重复元素且1~n中的所有元素均出现在to[i]中。
那么我们可以将to[i]看作一个置换。

根据置换在循环上的定义,我们可以通过不断让i=to[i]的方式让i重新回到i。
设经过的过程为 a 1 − > a 2 − > . . . − > a k − 1 − > a k − > a 1 a_1->a_2->...->a_{k-1}->a_k->a_1 a1>a2>...>ak1>ak>a1
那么有循环 ( a 1 , a 2 , . . . , a k , a 1 ) (a_1,a_2,...,a_k,a_1) (a1,a2,...,ak,a1)
若该循环包含了1到n所有元素,则枚举停止。否则从余下的元素中任一元素开始,如上述方法进行,再得一循环,如此反复直到所有元素都取完为止。
可以发现上述过程只经过n个点,时间复杂度为O(n)。

而我们swap(to[i],to[to[i]])的过程其实是对这个循环的一次转动。
即把( a i , a i + 1 , . . . a k , a 1 , . . . , a i − 1 a_i,a_{i+1},...a_k,a_1,...,a_{i-1} ai,ai+1,...ak,a1,...,ai1)转成( a i + 1 , . . . a k , a 1 , . . . , a i − 1 , a i a_{i+1},...a_k,a_1,...,a_{i-1},a_i ai+1,...ak,a1,...,ai1,ai)
那么根据循环定义,我们只要成功使其中任意 a i a_i ai使得 t o [ a i ] = a i to[a_i]=a_i to[ai]=ai,则有整个循环的 a 1 − a k a_1-a_k a1ak都有 t o [ a j ] = a j ( 1 < = j < = k ) to[a_j]=a_j(1<=j<=k) to[aj]=aj(1<=j<=k)

而且对于交换x,y两行(这里指奇或偶数行的第x,y行),首先将x移到y位置需要
y-x次翻转,再把y移到x位置需要y-x-1次,总步数为2(y-x)-1为奇数,那么必然改变另一边的奇偶性(即x为奇数时改变偶数,偶数时改变奇数)。

所以上述结论正确。
其实这是根据置换的优秀性质而设计出的方式而不是先写出方式再论证它正确。

还有个更sb的做法。
直接枚举头to[i],交换一次to[i]和to[to[i]],虽然没有本质不同,但更直接。


Code

#include<bits/stdc++.h>
using namespace std;
int a[100010][3];
int to[100010],n;
int t[2];
int main(){
	scanf("%d",&n);
	for(int i=0;i<3;i++)
	for(int j=1;j<=n;j++)
	scanf("%d",&a[j][i]);
	for(int i=1;i<=n;i++){
		to[i]=a[i][1]/3+1;
		if(a[i][1]%3!=2||((a[i][0]!=a[i][1]+1||a[i][2]!=a[i][1]-1)&&(a[i][0]!=a[i][1]-1||a[i][2]!=a[i][1]+1))||i%2!=to[i]%2){
			printf("No");
			return 0;
		}
		if(a[i][0]>a[i][2]) t[i&1]^=1; 
	}
	for(int i=1;i<=n;i++)
	while(to[i]!=i){
		t[i&1^1]^=1;
		swap(to[i],to[to[i]]);
	}
	if(t[0]||t[1]) printf("No");
	else printf("Yes");
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值