题目链接
测试点1~10
观察到$qle 500$,那我们duck只用暴力维护$500$行和最后一列。
换种说法,对行离散化,每次暴力取出数并且平移数组,时间复杂度和空间复杂度都是$O(nq)$。
测试点11~16
所有$x=1$,那就是只会用到第一行和最后一列。
思考一下,我们每次找到第$k$个数,放到最后面。
我们提取一个数出来,发现它后面的所有数的编号都会减一。
也许我们可以考虑用数据结构维护这些编号?然后每次询问就来一个二分查找,辅助数组存数值。看起来很可做。
初始每个位置上存一,每次用前缀和二分找到一个位置,单点减一,然后序列长度增加,最后这个位置加一。
用树状数组维护。
代码1(80分):

#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> #include<queue> #include<map> #include<set> #define IL inline #define RG register #define _1 first #define _2 second using namespace std; typedef long long LL; #define RI RG int #define RL RG LL const int N=3e5; const int N1=5e4; const int Q1=500; IL void qr(RI &x){ x=0; RG char ch=getchar(); while(!isdigit(ch)) ch=getchar(); for(;isdigit(ch);ch=getchar()) x=(x<<3)+(x<<1)+ch-'0'; } IL void qw(RL x,RG char ch){ RI k=0,s[13]; if(!x) s[k=1]=0; else for(;x;x/=10) s[++k]=x%10; for(;k;k--) putchar(s[k]+'0'); putchar(ch); } int n,m,q; struct Pr{ int x,y,d; }a[N+3]; int t1[Q1+3]; IL int bin1(RI l,RI r,RI x){ RI mid,ans; while(l<=r){ mid=(l+r)>>1; if(x<=t1[mid]){ r=mid-1; ans=mid; } else l=mid+1; } return ans; } LL p1[Q1+3][N1+3]; IL void sol1(){ for(RI i=1;i<=q;i++) t1[i]=a[i].x; sort(t1+1,t1+q+1); RI mm=1; for(RI i=2;i<=q;i++) if(t1[i]!=t1[i-1]) t1[++mm]=t1[i]; for(RI i=1;i<=q;i++) a[i].x=bin1(1,mm,a[i].x); for(RI i=1;i<=mm;i++) for(RI j=1;j<m;j++) p1[i][j]=(LL)(t1[i]-1)*m+j; for(RI i=1;i<=n;i++) p1[0][i]=(LL)i*m; for(RI k=1;k<=q;k++){ RL tmp; if(a[k].y==m) tmp=p1[0][t1[a[k].x]]; else tmp=p1[a[k].x][a[k].y]; qw(tmp,' '); for(RI i=a[k].y;i<m-1;i++) p1[a[k].x][i]=p1[a[k].x][i+1]; p1[a[k].x][m-1]=p1[0][t1[a[k].x]]; for(RI i=t1[a[k].x];i<n;i++) p1[0][i]=p1[0][i+1]; p1[0][n]=tmp; } } IL bool check2(){ for(RI i=1;i<=q;i++) if(a[i].x!=1) return false; return true; } int k,s2[N*3+3]; LL t2[N*3+2]; IL int lowbit(RI x){return x&(-x);} IL void mdf(RI p,RI x){ for(;p<=n+m+q;p+=lowbit(p)) s2[p]+=x; } IL int qry(RI p){ RI ret=0; for(;p;p-=lowbit(p)) ret+=s2[p]; return ret; } IL int bin2(RI l,RI r,RI x){ RI mid,ans; while(l<=r){ mid=(l+r)>>1; if(x<=qry(mid)){ r=mid-1; ans=mid; } else l=mid+1; } return ans; } IL void sol2(){ for(RI i=1;i<=n+m-1;i++) mdf(i,1); for(RI i=1;i<=m;i++) t2[i]=i; for(RI i=m+1,j=2;i<=n+m-1;i++,j++) t2[i]=(LL)j*m; k=n+m-1; for(RI i=1;i<=q;i++){ RI pos=bin2(1,k,a[i].y); qw(t2[pos],' '); mdf(pos,-1); mdf(++k,1); t2[k]=t2[pos]; } } IL void sol3(){ } int main(){ qr(n); qr(m); qr(q); for(int i=1;i<=q;i++){ qr(a[i].x); qr(a[i].y); a[i].d=i; } if(q<=500) sol1(); else if(check2()) sol2(); else sol3(); return 0; }
测试点17~20
首先,将最后一列单独拿出来,用代码1中的算法维护。
然后发现,操作可以分为两类:
- $y=m$,直接在最后一列上维护;
- $y<m$,每行的所有操作放到一起,看起来也是类似的问题。
那么,假如我们把最后一列单独拿走,剩下的第$i$行延长$c_i$格($c_i$表示第$i$行的操作数),然后我们发现可以对所有行以及最后一列分别用树状数组维护。
注意到不能开$n+1$个树状数组来在线做,不然会爆空间。
如果每次单独拿出同一行的操作,发现也不能得到答案,因为存在时间跨度,最后一列的数值会流通,我们无从得知加入每一行的数值是哪些。
但是我们发现,单独拿出来做,虽然得不到数值关系,但是可以得到位置关系。也即,我们可以预处理出对于每行,扩容后的数列里,每次操作对应的位置。
扩容后的数列分为两部分:
- 下标小于$m$的,这部分是“原生”数值,可以直接算得。注意会爆int。
- 下标大于等于$m$的,设它的下标为$y$,那么它就是在第$y-m+1$次操作该行时加入的。考虑到$c_i$的总和是$q$,我们可以开$n$个vector来存。注意vector的下标是从0开始的,所以$y$下标的询问在vector中的下标是$y-m$。
上面的差不多就是主要内容了。再来理清一下思路:
- 依次对于$x$相同的询问,利用树状数组,按照时间顺序预处理出在“扩容数列”里的下标(询问$(x,m)$的标记为操作最后一列然后不参与这部分的处理);
- 利用树状数组维护最后一列的流动情况,然后每次按时间顺序对询问分情况处理:答案怎么算,需不需要插入vector等等。
再补充一个细节:预处理的时候,每次记录修改树状数组的位置,每新开一行就按照记录复原修改,用总共$O(q)$的时间来完成每行的初始化。
代码2(100分):

#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> #define IL inline #define RG register using namespace std; typedef long long LL; typedef unsigned int UI; #define RI RG int #define RC RG char #define RL RG LL #define RU RG UI const int N=3e5; int n,m,q; struct Qry{ int x,y,d; }a[N+3]; IL bool cmp1(Qry A,Qry B){ return A.x==B.x?A.d<B.d:A.x<B.x; } IL bool cmp2(Qry A,Qry B){ return A.d<B.d; } int lim,s[N*2+3]; IL int lowbit(RI x){ return x&(-x); } IL void mdf(RI p,RI x){ for(;p<=lim;p+=lowbit(p)) s[p]+=x; } IL int qry(RI p){ RI ret=0; for(;p;p-=lowbit(p)) ret+=s[p]; return ret; } IL int sol(RI x){ RI l=1,r=lim,ans,mid; while(l<=r){ mid=(l+r)>>1; if(x<=qry(mid)){ r=mid-1; ans=mid; } else l=mid+1; } return ans; } vector<int>rec; vector<LL>t[N+3]; LL ans[N*2+3]; int main(){ scanf("%d%d%d",&n,&m,&q); for(RI i=1;i<=q;i++){ scanf("%d%d",&a[i].x,&a[i].y); a[i].d=i; } sort(a+1,a+q+1,cmp1); lim=m+q; for(RI i=1;i<m;i++) mdf(i,1); for(RI i=1,p=m-1,pos;i<=q;i++){ if(a[i].x!=a[i-1].x){ for(UI j=0;j<rec.size();j++) mdf(rec[j],1); while(p>=m){ mdf(p,-1); p--; } rec.clear(); } if(a[i].y==m){ a[i].y=-1; continue; } pos=sol(a[i].y); rec.push_back(pos); mdf(pos,-1); mdf(++p,1); a[i].y=pos; } sort(a+1,a+q+1,cmp2); memset(s,0,(m+q+1)*sizeof(int)); lim=n+q; for(RI i=1;i<=n;i++){ mdf(i,1); ans[i]=(LL)i*m; } RL tmp; for(RI i=1,p=n,pos;i<=q;i++) if(a[i].y==-1){ pos=sol(a[i].x); mdf(pos,-1); tmp=ans[pos]; ans[++p]=tmp; mdf(p,1); printf("%lld ",tmp); } else { pos=sol(a[i].x); mdf(pos,-1); if(a[i].y<m) tmp=(LL)(a[i].x-1)*m+a[i].y; else tmp=t[a[i].x][a[i].y-m]; t[a[i].x].push_back(ans[pos]); ans[++p]=tmp; mdf(p,1); printf("%lld ",tmp); } return 0; }