思路来源:http://blog.csdn.net/hhaannyyii/article/details/78619502;
首先我们将问题分解,将每行的前m-1个数提出来,最后一列提出来,那么每次操作都可以拆成几次删数和插数的操作;
这种东西显然可以用splay或区间线段树实现;但我们发现有n+1个数列,那就线段树动态开点吧;
就像主席树存某个前缀中权值在某个区间内的数有多少个一样,我们存某一行中权值在某个范围内的数被删了几个;
只不过主席树因为相邻的后一个只比前一个多一个数,空间复杂度是严格O(nlogn)的,而一般的线段树动态开点一次询问最多多开出log(n)个点;
所以空间复杂度是O(qlogn)的;
其实我们是把删除转化为在数组中把一个数直接变成0的,而插入是直接插在后面,我们在后面搞一个vector表就好了;
我们最后得到的是如果在矩阵中,那对应的就是它原来在矩阵中的位置,如果出去了就查表;
#include<iostream> #include<cstring> #include<cmath> #include<cstdio> #include<algorithm> #include<vector> using namespace std; typedef long long ll; const int maxn=300010; struct sege{ int v,l,r; }tr[maxn*30]; int tot,rt[maxn],q,N; ll n,m,ans; vector<ll>c[maxn]; void add(int l,int r,int& t,int rk){ if(!t)t=++tot; ++tr[t].v; if(l==r)return; int mid=(l+r)>>1; if(mid>=rk)add(l,mid,tr[t].l,rk); else add(mid+1,r,tr[t].r,rk); } ll qry(int l,int r,int t,int rk){ if(l==r)return l; int mid=(l+r)>>1; int res=mid-l+1-tr[tr[t].l].v; if(res>=rk)return qry(l,mid,tr[t].l,rk); else return qry(mid+1,r,tr[t].r,rk-res); } ll dl(ll x,ll y){ ll tmp=qry(1,N,rt[x],y); add(1,N,rt[x],tmp); if(tmp<m){return (x-1)*m+tmp;} else return c[x][tmp-m]; } ll dr(ll x){ ll tmp=qry(1,N,rt[n+1],x); add(1,N,rt[n+1],tmp); if(tmp<=n){return tmp*m;} else return c[n+1][tmp-n-1]; } int main(){ scanf("%d%d%d",&n,&m,&q); N=max(n,m)+q; ll x,y; while(q--){ scanf("%lld%lld",&x,&y); if(y==m){ans=dr(x);c[n+1].push_back(ans);printf("%lld\n",ans);} else{ ans=dl(x,y);c[n+1].push_back(ans); printf("%lld\n",ans); ans=dr(x);c[x].push_back(ans); } } // system("pause"); return 0; }
浙公网安备 33010602011771号