模拟13 题解
A. 矩阵游戏
通过前40%的部分分,我们发现程序复杂度不能为$O(nk)$。
设$h(i)$表示第$i$行最终乘的总系数,$l(i)$表示第$i$列。
考虑每个点$(i,j)$,它对最终答案的贡献是$((i-1)*m+j)*h(i)*l(j)$。
发现可以拆分为$(i-1)*m*h(i)*l(j)+j*l(j)*h(i)$。
维护$l(j),l(j)*j$的总数,枚举$i$即可$O(n+m)$解决。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #define ll long long 5 using namespace std; 6 const int N=1000100; 7 const int mod=1e9+7; 8 int n,m,k,x,y; 9 char opt; 10 ll h[N],l[N],sum,sumj; 11 int main(){ 12 scanf("%d%d%d",&n,&m,&k); 13 for(int i=1;i<=n;++i) h[i]=1; 14 for(int i=1;i<=m;++i) l[i]=1; 15 while(k--){ 16 scanf(" %c%d%d",&opt,&x,&y); 17 if(opt=='R') h[x]=h[x]*y%mod; 18 else l[x]=l[x]*y%mod; 19 } 20 ll ans=0; 21 for(int i=1;i<=m;++i) sum=(sum+l[i])%mod,sumj=(sumj+l[i]*i)%mod; 22 for(int i=1;i<=n;++i) ans=(ans+(1ll*(i-1)*m%mod*sum%mod+sumj)*h[i])%mod; 23 printf("%lld\n",ans); 24 return 0; 25 }
B. 跳房子
正解的思路是分块思想:
从矩阵中任意一点出发,一定存在长度不超过$n*m$的循环节。
用分块的思想,将长度为$m$分为一个块。
也就是说,我们维护第$1$列第$i$行走$m$步,会回到哪一行,并将此设为$jump(i)$。
$jump(i)$一定存在长度不超过$n$的循环节。
于是询问变得简单了,先暴力走到第1列,再用$jump$走到循环节,直接对循环节长度取模,
剩下的再走$jump$,走暴力即可。
但修改比较难弄。
另一个思路是线段树维护映射,其实使用的是倍增的思想:
建一棵下标区间为$m$的线段树,树上每个节点建立一个映射数组$map$。
映射数组大小为$n$,其中节点$[l,r]$上的$map(i)$表示第$i$行从$l$走$r-l+1$步到达的行数。
显然置换是满足结合律,也就是说,它可以进行快速幂。
对于修改操作,直接修改线段树上叶节点的置换,之后将置换上传。
对于询问操作,设步数为$k$。
查询走$m$步的置换,将$\lfloor \frac {k} {m} \rfloor *m$的部分直接快速幂,
$k$ $mod$ $m$的部分暴力即可。
复杂度$O(qnlogm)$。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 using namespace std; 5 const int N=2015; 6 int n,m,q,xn=1,yn=1,a[N][N],jump[N]; 7 struct map{ 8 int mp[N]; 9 void init(){ 10 for(int i=1;i<=n;++i) mp[i]=i; 11 } 12 }ans,tmp,as; 13 void mult(const map &a,const map &b,map *x){ 14 for(register int i=1;i<=n;++i) tmp.mp[i]=b.mp[a.mp[i]]; 15 memcpy(x,&tmp,sizeof(map)); 16 } 17 void qpow(map &base,int k,map *x){ 18 as.init(); 19 while(k){ 20 if(k&1) mult(as,base,&as); 21 mult(base,base,&base); 22 k>>=1; 23 } 24 memcpy(x,&as,sizeof(map)); 25 } 26 struct node{ 27 int l,r; 28 node *lch,*rch; 29 map s; 30 }pool[N<<1],*ptr=pool,*root; 31 inline int get(int x,int y){ 32 y=(y==m?1:y+1); 33 if(a[x][y]>a[x-1?x-1:n][y]&&a[x][y]>a[x+1<=n?x+1:1][y]) return x; 34 if(a[x-1?x-1:n][y]>a[x][y]&&a[x-1?x-1:n][y]>a[x+1<=n?x+1:1][y]) return x-1?x-1:n; 35 return x+1<=n?x+1:1; 36 } 37 void build(node *&p,int l,int r){ 38 p=new(ptr++) node(); 39 p->l=l; p->r=r; 40 if(l==r){ 41 for(register int i=1;i<=n;++i) p->s.mp[i]=get(i,l); 42 return ; 43 } 44 int mid=l+r>>1; 45 build(p->lch,l,mid); 46 build(p->rch,mid+1,r); 47 mult(p->lch->s,p->rch->s,&p->s); 48 } 49 void query(node *p,int l,int r,map *ans){ 50 if(p->l>=l&&p->r<=r){ 51 mult(*ans,p->s,ans); 52 return ; 53 } 54 if(l<=p->lch->r) query(p->lch,l,r,ans); 55 if(r>=p->rch->l) query(p->rch,l,r,ans); 56 } 57 void change(node *p,int pos){ 58 if(p->l==p->r){ 59 for(register int i=1;i<=n;++i) p->s.mp[i]=get(i,pos); 60 return ; 61 } 62 if(pos<=p->lch->r) change(p->lch,pos); 63 else change(p->rch,pos); 64 mult(p->lch->s,p->rch->s,&p->s); 65 } 66 inline int read(register int x=0,register char ch=getchar()){ 67 while(!isdigit(ch)) ch=getchar(); 68 while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); 69 return x; 70 } 71 int main(){ 72 n=read(); m=read(); 73 for(register int i=1;i<=n;++i) for(register int j=1;j<=m;++j) a[i][j]=read(); 74 build(root,1,m); 75 q=read(); 76 char opt; 77 for(register int i=1,x,y,k;i<=q;++i){ 78 do opt=getchar(); 79 while(opt!='m'&&opt!='c'); 80 if(opt=='m'){ 81 k=read(); ans.init(); 82 query(root,yn,m,&ans); if(yn-1) query(root,1,yn-1,&ans); 83 qpow(ans,k/m,&ans); k%=m; 84 if(k){ 85 if(yn+k-1<=m) query(root,yn,yn+k-1,&ans); 86 else query(root,yn,m,&ans),query(root,1,k-m+yn-1,&ans); 87 } 88 printf("%d %d\n",xn=ans.mp[xn],yn=(yn+k-1)%m+1); 89 } 90 else{ 91 x=read(); y=read(); k=read(); 92 a[x][y]=k; 93 change(root,y-1?y-1:m); 94 } 95 } 96 return 0; 97 }
C. 优美序列
容易想到的是一个对每次询问在线$O(n)$解决的算法。
设区间为$(l,r)$,$(l,r)$中的权值最小值和最大值为$(vl,vr)$。
如果$vr-vl!=r-l$,当前区间就不是一个优美序列。
如果最值$vl$,$vr$均出现在优美序列中,那么两者之间的所有数都应当出现。
因为优美序列是连续的,且对于每个数的位置只有一个,
两者之间的所有数出现,等价于两者之间所有数的位置最左值和位置最右值出现。
然而这样的话最值$vl$,$vr$可能会改变。
所以不断循环下去,直到满足优美序列条件时结束。
可以发现这个算法在随机数据下已经足够优秀,能够在1s之内解决问题了。
然而会被特殊构造的数据卡成真正的$O(n)$。
比如一个简单的数据,2 5 1 3 7 4 6。
手动模拟,当询问区间$(3,4)$的时候,需要循环n次才能得到结果。
所以这个算法被特殊构造的数据卡掉了,接下来是用分块思想对该算法的一个优化:
设区间$A$包含区间$B$,那么易证区间$A$的答案区间一定包含区间$B$的答案区间。
区间$B$的答案,可以直接更新区间$A$。
那么还需要解决的一个问题是怎么根据当前的区间找到合适的更新区间。
做法是分块。 将1~n分为$k$个块,每个块的大小为$q$,
枚举块的左右端点,共$k^2$次,算出一段连续的块的答案。
在循环的过程中,不断找到包含的最大连续块,尝试更新答案即可。
然而我觉得这个算法的复杂度仍然是$O(qn)$的,不过它成功过掉了出题人构造的数据。
1 #include<cstdio> 2 #include<cmath> 3 const int N=100100; 4 int n,m,a[N],pos[N],mnd[20][N],mxd[20][N],mnp[20][N],mxp[20][N],pw[N],q; 5 int lf[550][550],rf[550][550],bl[N]; 6 #define max(a,b) (a>b?a:b) 7 #define min(a,b) (a<b?a:b) 8 #define askmxp(l,r) max(mxp[pw[r-l+1]][l],mxp[pw[r-l+1]][r-(1<<pw[r-l+1])+1]) 9 #define askmnp(l,r) min(mnp[pw[r-l+1]][l],mnp[pw[r-l+1]][r-(1<<pw[r-l+1])+1]) 10 #define askmxd(l,r) max(mxd[pw[r-l+1]][l],mxd[pw[r-l+1]][r-(1<<pw[r-l+1])+1]) 11 #define askmnd(l,r) min(mnd[pw[r-l+1]][l],mnd[pw[r-l+1]][r-(1<<pw[r-l+1])+1]) 12 const int L=1<<20|1; 13 char buffer[L],*S,*T; 14 #define getchar() ((S==T&&(T=(S=buffer)+fread(buffer,1,L,stdin),S==T))?EOF:*S++) 15 inline int read(register int x=0,register char ch=getchar()) 16 { 17 while(ch<'0'||ch>'9') ch=getchar(); 18 while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar(); 19 return x; 20 } 21 int stack[30],top; 22 inline void out(register int x){ 23 do stack[++top]=x%10,x/=10; 24 while(x); 25 while(top) putchar(stack[top--]+48); 26 } 27 signed main(){ 28 n=read(); pw[0]=-1; q=pow(n,0.6666); 29 for(register int i=1;i<=n;++i){ 30 a[i]=read(); bl[i]=(i-1)/q+1; 31 pos[a[i]]=i; pw[i]=pw[i>>1]+1; 32 mnd[0][i]=a[i]; mxd[0][i]=a[i]; 33 mnp[0][a[i]]=i; mxp[0][a[i]]=i; 34 } 35 for(register short k=1;k<=pw[n];++k) 36 for(register int i=1;i<=n-(1<<k)+1;++i){ 37 mnd[k][i]=min(mnd[k-1][i],mnd[k-1][i+(1<<k-1)]); mxd[k][i]=max(mxd[k-1][i],mxd[k-1][i+(1<<k-1)]); 38 mnp[k][i]=min(mnp[k-1][i],mnp[k-1][i+(1<<k-1)]); mxp[k][i]=max(mxp[k-1][i],mxp[k-1][i+(1<<k-1)]); 39 } 40 for(register short i=1;i<=bl[n];++i){ 41 for(register short j=bl[n];j>=i;--j){ 42 register int l=(i-1)*q+1,r=min(j*q,n); 43 register int vl=askmnd(l,r),vr=askmxd(l,r); 44 while(vr-vl!=r-l){ 45 l=askmnp(vl,vr); r=askmxp(vl,vr); 46 if(bl[r]-1>j&&bl[l]+1<i){ 47 const register int tmp=l; 48 l=min(lf[bl[tmp]+1][bl[r]-1],l); 49 r=max(rf[bl[tmp]+1][bl[r]-1],r); 50 } 51 vl=askmnd(l,r); vr=askmxd(l,r); 52 } 53 lf[i][j]=l; rf[i][j]=r; 54 } 55 } 56 m=read(); 57 for(register int i=1,l,r,vl,vr;i<=m;++i){ 58 l=read(); r=read(); 59 vl=askmnd(l,r); vr=askmxd(l,r); 60 while(vr-vl!=r-l){ 61 l=askmnp(vl,vr); r=askmxp(vl,vr); 62 if(bl[r]-1>=bl[l]+1){ 63 const register int tmp=l; 64 l=min(lf[bl[tmp]+1][bl[r]-1],l); 65 r=max(rf[bl[tmp]+1][bl[r]-1],r); 66 } 67 vl=askmnd(l,r); vr=askmxd(l,r); 68 } 69 out(l); putchar(32); out(r); putchar(10); 70 } 71 return 0; 72 }