LGP10061 [SNTS 2024] 矩阵 学习笔记
LGP10061 [SNTS 2024] 矩阵 学习笔记
前言
大家好我非常喜欢根号算法所以我用根号过了这道题.jpg
隔了将近半年回来看这道题,终于看懂了。自己还是有进步的嘛?
大段摘抄此文。
题意简述
维护一个 \(n\times n\) 的矩阵 \(A\),行和列从 \(1\) 开始标号。一开始,有 \(A_{i,j}=(i+1)^j\bmod 998244353\)。
有 \(m\) 次操作,操作有两种:
- 将一个子方阵逆时针旋转 \(90\) 度。
- 将一个子矩阵内所有元素加上 \(d\)。
输出所有操作后的矩阵。为了减少输出量,输出 \((\sum_{i=1}^{n}\sum_{j=1}^{n}A_{i,j}\times{12345}^{(i-1)n+j}) \bmod 10^9+7\) 即可。
做法解析
发现旋转操作互相影响难以处理,尝试树形数据结构未果,发现操作间的互相影响并不太多,而且部分分中有 \(q\) 较小的数据但没有 \(n\) 较小的,考虑对操作分块,块内暴力划分子矩阵并修改,做完 \(B\) 个操作后暴力重构。具体地,我们每 \(B\) 个操作一块,块内对于一个操作,设它涉及了 \(([lx,rx],[ly,ry])\),我们就把所有子矩阵按照 \(lx-1\)、\(rx\)、\(ly-1\)、\(ry\) 各切一刀,这样可以保证每一个子矩阵一定在这次修改中完全被或不被修改。最后暴力重构就是把它们拼回原来那个大矩阵。
当然,时间复杂度是对的。对于 \(B\) 次操作,它们的 \(2B\) 个 \(x\) 坐标最多将坐标轴划分为 \(2x+1\) 段,\(y\) 坐标同理,因此矩阵最多被划分成 \((2x+1)^2\) 个子矩阵。注意这里的分析并不是将矩阵划分后旋转,而是每次询问时划分一下。所以当我们把询问分成大小为 \(B\) 的块,对于块中每次询问,暴力遍历目前所有子矩阵并进行划分或打标记就是 \(O(B^3)\) 的;又,我们在 \(B\) 个询问后重构时间复杂度是 \(O(n^2)\) 的。所以总复杂度:\(O(\frac{q}{B}(B^3+n^2))=O(qB^2+\frac{qn^2}{B})\)。取 \(B=n^{\frac{2}{3}}\) 时复杂度为 \(O(qn^{\frac{4}{3}})\),实现时可以取 \(B=2^6\)。
代码实现
想清楚你划分出来的子矩阵要维护几个信息?
这里的写法是:令 {ox,oy,xe,ye,px,py,d,t} 表示:当前子矩阵对应着原方阵中 \(([ox,ox+xe],[oy,oy+ye])\) 这一范围的元素;我们规定原方阵左上角为 \((1,1)\),向下为 \(x\) 增加,向右为 \(y\) 增加,则当前对应原方阵 \((ox,oy)\) 的元素(也即这一片的原左上角)实际处在 \((px,py)\) 这一坐标;且其已经绕着 \((px,py)\) 逆时针旋转了 \(d\) 个 \(90\) 度,当前被整体加上了 \(t\)。
你把这个搞懂了,加以一点点耐心就可以写完这题了!不难的!
所以我半年前竟然看不懂这个又算什么呢。
此代码实现也算作是优美。
可能你会觉得,两个 splits() 里面对于方向的分讨免不掉所以写出来的观感就是很史。你说得对,但是:
你如果有一双善于发现的眼睛,你就会发现,由对称性,你会发现 \(d=0/2\) 时,切割 \(x\) 和切割 \(y\) 是同构的。所以写了切割 \(x\) 时 \(d=0/2\) 的情况就可以直接搬到切割 \(y\) 时 \(d=0/2\) 的情况。
至于 \(d=1/3\)?其实,在切割 \(x\) 的时候,\(d=0/3\) 除了 \(px\) 之外,\(d=0\) 中所有对某某 \(x\) 的修改都对应到了 \(d=3\) 中对某某 \(y\) 的修改。原因不难理解,这两个方向中 \(px\) 都是在实际 \(x\) 坐标较小的一端。切割 \(x\) 时 \(d=1/2\) 的关系同理。而切割 \(y\) 时 \(d=0/1\) 和 \(d=2/3\) 的关系也同理。所以实际上这题八个分讨里面写完两个就可以一通复制粘贴,这题就炸过去了。
不过复制粘贴时要把细节注意好,不然炸的就是你自己的心态了。
\(\texttt{UPD1}\):这题把我调吐了。还没过。纯砂壁。
\(\texttt{UPD2}\):别搁这调了。留下 \(\texttt{10pts}\) 代码然后重写这一段吧。
\(\texttt{UPD3}\):啊终于过了。
#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=3e3+5,Ksiz=(1<<6),MaxD=2e4+5;
int N,M,A[MaxN][MaxN],Opt,X[2],Y[2],Z,ans,B[MaxN][MaxN];
int madd(int x,int y){return x+y>=M107?x+y-M107:x+y;}
struct anob{int ox,oy,xe,ye,px,py,d,t;}stk[MaxD];int ktp;
void adebug(){for(int i=1;i<=N;i++,puts(""))for(int j=1;j<=N;j++)writip(A[i][j]);}
void xsplits(int xx){
for(int id=ktp;id>=1;id--){
auto &[cox,coy,cxe,cye,cpx,cpy,cd,ct]=stk[id];
if(cd==0&&cpx<=xx&&xx<cpx+cxe){
int nxe=cpx+cxe-xx-1;cxe=xx-cpx;
int nox=cox+cxe+1,npx=xx+1;
stk[++ktp]={nox,coy,nxe,cye,npx,cpy,cd,ct};
}
if(cd==3&&cpx<=xx&&xx<cpx+cye){
int nye=cpx+cye-xx-1;cye=xx-cpx;
int noy=coy+cye+1,npx=xx+1;
stk[++ktp]={cox,noy,cxe,nye,npx,cpy,cd,ct};
}
if(cd==2&&cpx-cxe<=xx&&xx<cpx){
int nxe=xx-cpx+cxe;cxe=cpx-xx-1;
int nox=cox+cxe+1,npx=xx;
stk[++ktp]={nox,coy,nxe,cye,npx,cpy,cd,ct};
}
if(cd==1&&cpx-cye<=xx&&xx<cpx){
int nye=xx-cpx+cye;cye=cpx-xx-1;
int noy=coy+cye+1,npx=xx;
stk[++ktp]={cox,noy,cxe,nye,npx,cpy,cd,ct};
}
}
}
void ysplits(int yy){
for(int id=ktp;id>=1;id--){
auto &[cox,coy,cxe,cye,cpx,cpy,cd,ct]=stk[id];
if(cd==0&&cpy<=yy&&yy<cpy+cye){
int nye=cpy+cye-yy-1;cye=yy-cpy;
int noy=coy+cye+1,npy=yy+1;
stk[++ktp]={cox,noy,cxe,nye,cpx,npy,cd,ct};
}
if(cd==1&&cpy<=yy&&yy<cpy+cxe){
int nxe=cpy+cxe-yy-1;cxe=yy-cpy;
int nox=cox+cxe+1,npy=yy+1;
stk[++ktp]={nox,coy,nxe,cye,cpx,npy,cd,ct};
}
if(cd==2&&cpy-cye<=yy&&yy<cpy){
int nye=yy-cpy+cye;cye=cpy-yy-1;
int noy=coy+cye+1,npy=yy;
stk[++ktp]={cox,noy,cxe,nye,cpx,npy,cd,ct};
}
if(cd==3&&cpy-cxe<=yy&&yy<cpy){
int nxe=yy-cpy+cxe;cxe=cpy-yy-1;
int nox=cox+cxe+1,npy=yy;
stk[++ktp]={nox,coy,nxe,cye,cpx,npy,cd,ct};
}
}
}
void rebuild(){
for(int id=1;id<=ktp;id++){
auto [ox,oy,xe,ye,px,py,cd,ct]=stk[id];
for(int i=0;i<=xe;i++){
for(int j=0;j<=ye;j++){
int dx=i,dy=j;if(cd==1||cd==3)swap(dx,dy);
int bx=px+dx*((cd==0||cd==3)?1:-1);
int by=py+dy*((cd==0||cd==1)?1:-1);
B[bx][by]=madd(A[ox+i][oy+j],ct);
}
}
}
for(int i=1;i<=N;i++)for(int j=1;j<=N;j++)A[i][j]=B[i][j];
stk[ktp=1]={1,1,N-1,N-1,1,1,0,0};
}
bool check(int id){
int lx=stk[id].px,ly=stk[id].py,cd=stk[id].d;
int ex=stk[id].xe,ey=stk[id].ye;if(cd==1||cd==3)swap(ex,ey);
int rx=lx+ex*((cd==0||cd==3)?1:-1);if(lx>rx)swap(lx,rx);
int ry=ly+ey*((cd==0||cd==1)?1:-1);if(ly>ry)swap(ly,ry);
return X[0]<=lx&&rx<=X[1]&&Y[0]<=ly&&ry<=Y[1];
}
int main(){
readis(N,M);
for(int i=1;i<=N;i++){
for(int j=1,c=1;j<=N;j++){
A[i][j]=c=(c*1ll*(i+1)%M998);
}
}
stk[ktp=1]={1,1,N-1,N-1,1,1,0,0};
for(int m=1,kk;m<=M;m++){
readis(Opt,X[0],Y[0],X[1],Y[1]);
xsplits(X[0]-1),xsplits(X[1]);
ysplits(Y[0]-1),ysplits(Y[1]);
if(Opt==1){
int clen=X[1]-X[0]+1;
for(int id=1;id<=ktp;id++){
if(check(id)){
(stk[id].d+=1)&=3;
int dx=clen-stk[id].py+Y[0],dy=stk[id].px-X[0]+1;
stk[id].px=X[0]+dx-1,stk[id].py=Y[0]+dy-1;
}
}
}
if(Opt==2){
readi(Z);
for(int id=1;id<=ktp;id++)if(check(id))stk[id].t=madd(stk[id].t,Z);
}
if(m%Ksiz==0||m==M)rebuild();
}
for(int i=1,k=1;i<=N;i++){
for(int j=1;j<=N;j++){
k=k*12345ll%M107,ans=(ans+A[i][j]*1ll*k)%M107;
}
}
writi(ans);
return 0;
}
反思总结
有些科技不能陪伴你解决太多的难题,但根号可以。
原因:很多难题就是喜爱根号的毒瘤出题人端上来的。
遇事不决,操作序列分块。
后记
好吧,所以半年内我进步了多少呢?
能赢吗?爱橘信橘等橘子。橘子 \(\texttt{M1S6}\) 加油。
浙公网安备 33010602011771号