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 }