题意
题解
又是一道神仙题……
显然的做法是大力splay,时间复杂度\(O((N+Q)N\log N)\), 可以卡掉。
正解: 使用十字链表维护矩阵,在周围增加第\(0\)行/列和第\((n+1)\)行/列,设\(li[x][d]\)表示\(x\)这个点在\(d\)这个方向上的下一个元素的编号是什么(一开始给每个元素都编上号)。那么对于一次旋转,子矩形边界上的格子暴力修改,内部相当于把\(4\)个方向做了个轮换,因此可以打标记实现。
然而本题的实现方法比较神奇: 每次修改从\((0,0)\)走到\((x,y)\) (只花费\(O(N)\)的时间),首先\((0,0)\)的标记一定是正确的(因为没有修改过),然后一路上通过当前点和下一个点互相储存位置的偏移量以及当前点的正确标记确定下一个点的正确标记。(详见代码,我的代码里标记的含义是实际方向等于存储方向加标记)
时间复杂度\(O((N+Q)N)\).
代码
#include<bits/stdc++.h>
#define llong long long
using namespace std;const int N = 1002;
const int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
char ch[N+3];
char a[N*N+3];
int li[N*N+3][4];
int tag[N*N+3];
int aux1[4][N+3],aux2[4][N+3];
int n,q;int getid(int x,int y) {return x*(n+2)+y+1;}
int getnxt(int u,int dir)
{int ret = li[u][(dir-tag[u]+4)&3];for(int i=0; i<4; i++) {if(li[ret][i]==u) {tag[ret] = (dir-i+6)&3;}}return ret;
}int main()
{scanf("%d%d",&n,&q);for(int i=1; i<=n; i++) {scanf("%s",ch+1); for(int j=1; j<=n; j++) a[getid(i,j)] = ch[j];}for(int i=0; i<=n+1; i++){for(int j=0; j<=n+1; j++){int u = getid(i,j);for(int k=0; k<4; k++){if(i+dx[k]>=0&&i+dx[k]<=n+1&&j+dy[k]>=0&&j+dy[k]<=n+1) {li[u][k] = getid(i+dx[k],j+dy[k]);}}}}while(q--){int x,y,l; scanf("%d%d%d",&x,&y,&l);int u = 1;for(int i=0; i<x; i++) u = getnxt(u,0);for(int i=0; i<y; i++) u = getnxt(u,1);for(int k=0; k<4; k++){for(int i=0; i<l; i++){aux1[k][i] = u; aux2[k][i] = getnxt(u,(k+3)&3);if(i<l-1) u = getnxt(u,k);}}for(int k=0; k<4; k++){for(int i=0; i<l; i++){li[aux1[k][i]][(k-tag[aux1[k][i]]+3)&3] = aux2[(k+1)&3][i];li[aux2[k][i]][(k-tag[aux2[k][i]]+5)&3] = aux1[(k+3)&3][i];}}}int u = 1;for(int i=1; i<=n; i++){u = getnxt(u,0);int uu = u;for(int j=1; j<=n; j++) {uu = getnxt(uu,1); ch[j] = a[uu];/* printf("%d ",uu);*/}puts(ch+1);
// puts("");}return 0;
}