Description
给你一个3*N的网格,位置为(i,j)的网格上的数为i+3(j-1)。每次选一个3*3的网格旋转180度,问最后能否使得网格(i,j)的值为ai,j。(5≤N≤105)
如图:
Solution
依图可看出,所谓的旋转就是将选择的3*3网格左右列交换,并且3列都进行翻转。
设正列(如1,2,3)为小写字母,反列(如3,2,1)为大写字母。
假如有相邻5列:
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
我们可以在有5列可供操纵的情况下将任意相隔1列的两列翻转而不影响其他。
在最终答案中设下标为奇的反列个数为x,下表为偶的个数为y。
先不考虑翻转问题,将奇列和偶列分开考虑(因为在处理奇列的时候只会翻转却不会影响偶列的具体数值)。由于如果初始矩阵操作后变为矩阵a,则矩阵a一定能变为初始矩阵,我们按照列的权值从小到大将矩阵a恢复为初始矩阵。处理奇列时,每翻转一次,就会翻转一个偶列,偶列也是同样道理。我们计算出奇列、偶列被翻转次数的奇偶性,与x、y比较即可。
Code
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> using namespace std; int n,a[3][100010]; int need_swap[2],real_swap[2]; int num[100010],id[100010]; int tree[100010]; void add(int _id,int x){for(;_id<=n;_id+=_id&-_id) tree[_id]+=x;} int query(int _id){int re=0;for(;_id;_id-=_id&-_id) re+=tree[_id];return re;} int main() { scanf("%d",&n); for (int j=0;j<3;j++) for (int i=1;i<=n;i++) scanf("%d",&a[j][i]); for (int i=1;i<=n;i++) for (int j=0;j<3;j++) { if ((a[0][i]^i)&1) { printf("No");return 0;} if (a[0][i]==a[1][i]+1&&a[1][i]==a[2][i]+1&&a[2][i]%3!=0) { need_swap[i&1]++;num[i]=a[2][i]/3+1;id[num[i]]=i;continue; } if (a[0][i]==a[1][i]-1&&a[1][i]==a[2][i]-1&&a[0][i]%3!=0) { num[i]=a[0][i]/3+1;id[num[i]]=i;continue; } printf("No");return 0; } int cnt; for (int i=1;i<=n;i+=2) { cnt=id[i]+2*(i/2-query(id[i])); real_swap[0]+=abs(i-cnt)/2; add(id[i],1); } memset(tree,0,sizeof(tree)); for (int i=2;i<=n;i+=2) { cnt=id[i]+2*(i/2-1-query(id[i])); real_swap[1]+=abs(i-cnt)/2; add(id[i],1); } real_swap[0]%=2;real_swap[1]%=2;need_swap[0]%=2;need_swap[1]%=2; if (real_swap[0]!=need_swap[0]||real_swap[1]!=need_swap[1]) printf("No"); else printf("Yes"); }