20190805 NOIP模拟测试13 「矩阵游戏 · 跳房子 · 优美序列」
考砸了 92分 rank29/63
只T1,T3打了两个暴力,一个40,一个52,成功地滚回了第二机房,没啥好说的,题挺棒的,我太菜了
A. 矩阵游戏
不是水题,
思路简单但是一下子想不出来,
hang[i]表示i行应该要乘几,lie[j]表示j列要乘几
对于每个点的贡献,是点的值乘上hang[i]再乘lie[j],点的值是(i-1)*m+j
那么 $ ans=\sum\limits_{i=1}^{n} hang[i]*\sum\limits_{j=1}^{m}lie[j] * ((i-1)*m+j) $
化简 $ans=\sum\limits_{i=1}^{n}hang[i]*(i-1)*m * \sum\limits_{j=1}^m lie[j] + \sum\limits_{j=1}^mlie[j]*j*\sum\limits_{i=1}^n $
O(n)求出$ sumhang=\sum\limits_{i=1}^nhang[i],sumlie=\sum\limits_{j=1}^mlie[j] $
最终$ ans=\sum\limits_{i=1}^{n}hang[i]*(i-1)*m *sumlie+ \sum\limits_{j=1}^mlie[j]*j*sumhang $
#include<iostream> #include<cstdio> using namespace std; int n,m,K; const int mod=1e9+7; long long hang[1100000],lie[1100000]; int main(){ scanf("%d%d%d",&n,&m,&K); for(int i=1;i<=max(n,m);i++) hang[i]=lie[i]=1; char opt[5]; long long x,y; for(int i=1;i<=K;i++){ scanf("%s%lld%lld",opt,&x,&y); if(opt[0]=='R') hang[x]=hang[x]*y%mod; else lie[x]=lie[x]*y%mod; } long long sumh=0,suml=0; for(int i=1;i<=n;i++) sumh=(sumh+hang[i])%mod; for(int i=1;i<=m;i++) suml=(suml+lie[i])%mod; long long ans=0; for(int i=1;i<=n;i++) ans=(ans+hang[i]*(i-1)%mod*m%mod*suml%mod)%mod; for(int i=1;i<=m;i++) ans=(ans+lie[i]*i%mod*sumh%mod)%mod; printf("%lld\n",ans); }
B.跳房子
大模拟,jump[i]表示第1列第i行的点蹦到第m列再蹦到第1列的位置,这个操作跳了m步,可以想到这样一直跳啊跳,会出环,而且一直在第1列徘徊
求出在环里走一圈的所需步数设为num,注意求的时候每一跳是m步,num为环内点数*m
设当前所在位置 px,py,一开始px=py=1;
首先考虑move操作,可以分成五部分:
第一部分 : 从(px,py)暴力走到第一列的某一行,这是jump的开始(如果就在第一列就不用走了)
第二部分: 从第一列的某一行,一直跳,跳进环里(如果就在环里,还是不用跳)
ps:此时的剩余步数要减去前两部分所走的步数(第一部分,一步一步;第二部分,一跳m步)
第三部分: 用剩余步数C %=num, 表示不断沿环飞,每次都又回到了原地(有点傻逼~)
第四部分 : 剩余步数(C<num)不够飞一圈了,就改成跳,每次消耗m步
第五部分:剩余步数(C<m) 不够跳一大步了,一步步爬吧,直到爬到ans
然后考虑change操作,每次改一个点的值,设这个点为(x,y),那么它所影响的只有三个点:左上(x-1,y-1),左(x,y-1),左下(x+1,y-1)
对于这三个点需要修改的情况只有两种:1.原来向(x,y)走,现在不能走了 2. 原来不能走(x,y),现在要走
先设当前修改的点(x,y)向上一格的点xup1,两格xup2,向下一格xdown1,向下两格xdown2,处理出这五个点的向右走最终到达第一列的哪一个位置
然后对左上,左,左下三个点分别进行判断: 看现在应该向xup1,xup2,x,xdown1,xdown2哪一个点走(注意是原来没有从这儿走,现在要走),然后从这三个点分别进入update函数
为了讲清楚,此处将三个点编号a1,a2,a3,将三个点从那五个点里选择而最终走到的一个第一列的点编号to1,to2,to3
使劲往回搜,看第一列的哪些点会走到这儿,为了降低时间,我们向左上深搜,向左下深搜,找到第一列会走到这个点左上角和左上角,将这一区间的jump赋成to
举个锤子,从点a1找左上角,左下角,然后把第一列从上角到下角的所有的jump改成to1
那么为什么左上角到左下角之间都赋成to是正确的呢?看图:
如果1->2是对的,那么3不可能指向4,因为如果3指向4,那么代表4的权值大于点2,那么1应该指向4,所以不可能交叉
那么再想一下,如果我找到了一个左上,左下,那么从左上一定能走到a1,左下一定能走到a1,这就围成了一个“三角形”,里边的任何一个点都不可能越过“三角形”的边,而a1是最右边的顶点,全部的点又都是向右跳且不会跳出”三角形“,最后必然会经过点a1,所以左上左下区间内的所有的jump改成to1
如此庞大而复杂的修改操作完成了,Lockey把代码稍稍简化了一下,删去了一些没用的东西,刚好200行
#include<iostream> #include<cstdio> #include<cstring> using namespace std; #define Re register int n,m,Q; int a[2100][2100],v[2100],st[2100],inst[2100],is_c[2100],jump[2100],num,la; int px=0,py=0; char opt[10]; inline int read(){ register int ret; register char r; while(r=getchar(),r<'0'||r>'9');ret=r-48; while(r=getchar(),r>='0'&&r<='9')ret=ret*10+r-48; return ret; } inline int dfs1(int x,int y){ int xup=(x-1+n)%n,xdown=(x+1+n)%n; if(y==m-1) return a[x][0]>a[xup][0]?(a[x][0]>a[xdown][0]?x:xdown):(a[xup][0]>a[xdown][0]?xup:xdown); return a[x][y+1]>a[xup][y+1]?(a[x][y+1]>a[xdown][y+1]?dfs1(x,y+1):dfs1(xdown,y+1)):(a[xup][y+1]>a[xdown][y+1]?dfs1(xup,y+1):dfs1(xdown,y+1)); } inline void walk(int &c,int opt){ if(opt==1){ while(py!=0&&c){ c--; py=(py+1)%m; int xup=(px-1+n)%n,xdown=(px+1+n)%n; if(a[px][py]>a[xup][py]&&a[px][py]>a[xdown][py]) px=px; else if(a[xup][py]>a[px][py]&&a[xup][py]>a[xdown][py]) px=xup; else if(a[xdown][py]>a[px][py]&&a[xdown][py]>a[xup][py]) px=xdown; } while(!is_c[px]&&c>=m){ c-=m; px=jump[px]; } } else{ while(c>=m){ c-=m; px=jump[px]; } while(c){ c--; py=(py+1)%m; int xup=(px-1+n)%n,xdown=(px+1+n)%n; if(a[px][py]>a[xup][py]&&a[px][py]>a[xdown][py]) px=px; else if(a[xup][py]>a[px][py]&&a[xup][py]>a[xdown][py]) px=xup; else if(a[xdown][py]>a[px][py]&&a[xdown][py]>a[xup][py]) px=xdown; } } } inline void dfs2(int x){ v[x]=inst[x]=1; st[++st[0]]=x; if(inst[jump[x]]){ int y; num=0; do{ y=st[st[0]--]; inst[y]=0; is_c[y]=1; num+=m; }while(y!=jump[x]); } else if(!v[jump[x]]) dfs2(jump[x]); inst[x]=0; } int update1(int x,int y){ if(y==0) return x; int xu1=(x-1+n)%n,xd1=(x+1+n)%n; int xu2=(xu1-1+n)%n,xd2=(xd1+1+n)%n; int l=-1; if(a[x][y]>a[xu1][y]&&a[x][y]>a[xu2][y]) l=update1(xu1,y-1); if(l!=-1) return l; if(a[x][y]>a[xu1][y]&&a[x][y]>a[xd1][y]) l=update1(x,y-1); if(l!=-1) return l; if(a[x][y]>a[xd1][y]&&a[x][y]>a[xd2][y]) l=update1(xd1,y-1); return l; } int update2(int x,int y){ if(y==0) return x; int xu1=(x-1+n)%n,xd1=(x+1+n)%n; int xu2=(xu1-1+n)%n,xd2=(xd1+1+n)%n; int r=-1; if(a[x][y]>a[xd1][y]&&a[x][y]>a[xd2][y]) r=update2(xd1,y-1); if(r!=-1) return r; if(a[x][y]>a[xu1][y]&&a[x][y]>a[xd1][y]) r=update2(x,y-1); if(r!=-1) return r; if(a[x][y]>a[xu1][y]&&a[x][y]>a[xu2][y]) r=update2(xu1,y-1); return r; } inline void update(int x,int y,int to){ if(y==0){ jump[x]=to; return; } int xu1=(x-1+n)%n,xd1=(x+1+n)%n; int xu2=(xu1-1+n)%n,xd2=(xd1+1+n)%n; int l=-1,r=-1; if(a[x][y]>a[xu1][y]&&a[x][y]>a[xu2][y]){ l=update1(xu1,y-1); } if(a[x][y]>a[xu1][y]&&a[x][y]>a[xd1][y]){ if(l==-1) l=update1(x,y-1); } if(a[x][y]>a[xd1][y]&&a[x][y]>a[xd2][y]){ if(l==-1) l=update1(xd1,y-1); } if(a[x][y]>a[xd1][y]&&a[x][y]>a[xd2][y]){ r=update2(xd1,y-1); } if(a[x][y]>a[xu1][y]&&a[x][y]>a[xd1][y]){ if(r==-1) r=update2(x,y-1); } if(a[x][y]>a[xu1][y]&&a[x][y]>a[xu2][y]){ if(r==-1) r=update2(xu1,y-1); } if(l==-1) return; if(l>r){ for(int i=l;i<n;i++) jump[i]=to; for(int i=0;i<=r;i++) jump[i]=to; } else for(int i=l;i<=r;i++) jump[i]=to; } inline void change(int x,int y){ int xu1=(x-1+n)%n,xd1=(x+1+n)%n; int xu2=(xu1-1+n)%n,xd2=(xd1+1+n)%n; int ny=(y-1+m)%m; int to,toup1,toup2,todown1,todown2; if(y!=0) to=dfs1(x,y); else to=x; if(y==0){ toup1=xu1,toup2=xu2; todown1=xd1,todown2=xd2; } else{ toup1=dfs1(xu1,y); toup2=dfs1(xu2,y); todown1=dfs1(xd1,y); todown2=dfs1(xd2,y); } if(a[xu1][y]>la||a[xu2][y]>la){ if(a[x][y]>a[xu1][y]&&a[x][y]>a[xu2][y]) update(xu1,ny,to); } else if(a[xu1][y]<la&&a[xu2][y]<la){ if(a[x][y]<a[xu1][y]||a[x][y]<a[xu2][y]){ update(xu1,ny,a[xu1][y]>a[xu2][y]?toup1:toup2); } } if(a[xu1][y]>la||a[xd1][y]>la){ if(a[x][y]>a[xu1][y]&&a[x][y]>a[xd1][y]) update(x,ny,to); } else if(a[xu1][y]<la&&a[xd1][y]<la){ if(a[x][y]<a[xu1][y]||a[x][y]<a[xd1][y]){ update(x,ny,a[xu1][y]>a[xd1][y]?toup1:todown1); } } if(a[xd1][y]>la||a[xd2][y]>la){ if(a[x][y]>a[xd1][y]&&a[x][y]>a[xd2][y]) update(xd1,ny,to); } else if(a[xd1][y]<la&&a[xd2][y]<la){ if(a[x][y]<a[xd1][y]||a[x][y]<a[xd2][y]){ update(xd1,ny,a[xd1][y]>a[xd2][y]?todown1:todown2); } } } int main(){ n=read(),m=read(); for(Re int i=0;i<n;i++) for(Re int j=0;j<m;j++) a[i][j]=read(); for(Re int i=0;i<n;i++) jump[i]=dfs1(i,0); for(Re int i=0;i<n;i++) if(!v[i]) dfs2(i); scanf("%d",&Q); int x,y,c; while(Q--){ scanf("%s",opt); if(opt[0]=='m'){ c=read(); walk(c,1); c%=num; walk(c,0); printf("%d %d\n",px+1,py+1); } else{ x=read(),y=read(),c=read(); x--,y--; la=a[x][y]; a[x][y]=c; change(x,y); for(int i=0;i<n;i++) v[i]=is_c[i]=0; st[0]=0; for(int i=0;i<n;i++) if(!v[i]) dfs2(i); } } }
C. 优美序列
ST表维护查询区间[l,r]的最大值与最小值,以及值域区间[l,r]的最左最右位置
查询时由区间[l,r]查询值域[ll,rr],在由值域区间[ll,rr]查询区间更新[l,r],反复操作,知道r-l==rr-ll时,l,r即答案
降低时间复杂度可以用分块或线段树,我是用的分块,有一个优化操作(当然就是分块优化的那个操作),对于在[l,r]区间内部的块的最短优美区间如果包含[l,r],那么这个优美区间就是[l,r]的最短优美区间,证明如下:
优美区间定义是区间内所有数排列后是连续的,那么假设[l,r]内部的一个块找到的最短优美区间为[s,t]且s<=l,t>=r, 那么[s,t]内部的数排列后是连续的,如果有[l,r]的优美区间[ss,tt]比[s,t]更短(更优),那么[ss,tt]一定包含[l,r]中的那一个块,因此这个块的最短优美区间就应该是[ss,tt]而不是[s,t],所以这个块的最短优美区间且包含[l,r]区间的区间[s,t]就是[l,r]的最短优美区间,直接跳出循环,输出答案即可
#include<bits/stdc++.h> using namespace std; const int N=110000; int n,m,l,r,ll,rr,t,piece_num,piece_len, a[N],len[20],Log[N], maxn1[20][N],minn1[20][N],maxn2[20][N],minn2[20][N],//maxn1,minn1,记录区间[ij]中的值域最大和最小,maxn2,minn2记录值域[ij]中位置最右和最左 mpx[2100][2100],mpy[2100][2100],belong[N];//记录块i与块j之间的区间能达到的最短优美区间的左右端点,记录i点属于哪个块 inline void read(register int &ret){ register char r=getchar(); for(ret=0;r<48||r>57;r=getchar()); while(r>=48&&r<=57)ret=(ret<<1)+(ret<<3)+(r^48),r=getchar(); return; } int main(){ read(n); piece_len=pow(n,0.666),piece_num=n/piece_len; for(register int i=1;i<=n;i++) read(a[i]), belong[i]=(i-1)/piece_len+1, maxn1[0][i]=minn1[0][i]=a[i], maxn2[0][a[i]]=minn2[0][a[i]]=i; Log[0]=-1,len[0]=1; for(register int i=1;i<=n;i++) Log[i]=Log[i>>1]+1; for(register int i=1;i<20;i++) len[i]=len[i-1]<<1; for(register int i=1;len[i]<=n;i++){ for(register int j=1;j+len[i]-1<=n;j++){ maxn1[i][j]=max(maxn1[i-1][j],maxn1[i-1][j+len[i-1]]); maxn2[i][j]=max(maxn2[i-1][j],maxn2[i-1][j+len[i-1]]); minn1[i][j]=min(minn1[i-1][j],minn1[i-1][j+len[i-1]]); minn2[i][j]=min(minn2[i-1][j],minn2[i-1][j+len[i-1]]); } } for(register int i=1;i<=piece_num;i++){ l=(i-1)*piece_len+1,r=i*piece_len; t=Log[r-l+1]; ll=min(minn1[t][l],minn1[t][r-len[t]+1]); rr=max(maxn1[t][l],maxn1[t][r-len[t]+1]); while(rr-ll!=r-l){ t=Log[r-l+1]; ll=min(minn1[t][l],minn1[t][r-len[t]+1]); rr=max(maxn1[t][l],maxn1[t][r-len[t]+1]); t=Log[rr-ll+1]; l=min(minn2[t][ll],minn2[t][rr-len[t]+1]); r=max(maxn2[t][ll],maxn2[t][rr-len[t]+1]); } mpx[i][i]=l,mpy[i][i]=r; } for(register int i=1;i<=piece_num;i++) for(register int j=i+1;j<=piece_num;j++) mpx[i][j]=min(mpx[i][j-1],mpx[j][j]),mpy[i][j]=max(mpy[i][j-1],mpy[j][j]); read(m); while(m--){ read(l),read(r); t=Log[r-l+1]; ll=min(minn1[t][l],minn1[t][r-len[t]+1]); rr=max(maxn1[t][l],maxn1[t][r-len[t]+1]); while(rr-ll!=r-l){ int ss=belong[l],tt=belong[r]-1; if(ss+1<=tt-1&&mpx[ss+1][tt-1]<=l&&mpy[ss+1][tt-1]>=r){ l=mpx[ss+1][tt-1],r=mpy[ss+1][tt-1]; break; } t=Log[r-l+1]; ll=min(minn1[t][l],minn1[t][r-len[t]+1]); rr=max(maxn1[t][l],maxn1[t][r-len[t]+1]); t=Log[rr-ll+1]; l=min(minn2[t][ll],minn2[t][rr-len[t]+1]); r=max(maxn2[t][ll],maxn2[t][rr-len[t]+1]); } printf("%d %d\n",l,r); } }