[NOIP2017] 列队
题意:
有一个$n\times m$的方阵,方阵里的人从上到下,从左到右编号为$1-nm$。
有q次操作,每次操作分为如下步骤:
- 先将位置$(x,y)$的人拿出来;
- 然后让$(x,y)$右边的人整体左移补齐空位;
- 再让$(x,m)$下边的人整体上移补齐空位;
- 最后将一开始拿出来的人放到$(n,m)$。
请你求出每次操作拿出来的人的编号。
$n,m,q\leq 3\times 10^{5}$。
题解:
首先考场上80分是送的,注意到这是个裸的区间分裂/合并,所以Splay做法也是送的,但是会被卡。
仔细考察一下这个有点复杂的操作,我们可以把它分成两个部分。
- 在第x行删一个数,再往后面填一个数。
- 在第m列删一个数,再往后面填一个数。
那么我们只要能解决“在一个序列里删一个数,再往后面填一个数”就行了。
如果Splay的话是“删一个区间,填一个区间”,有点大材小用,我们考虑树状数组做一下。
有一个简单的套路:不考虑填的数是什么,只考虑下标,相当于每次单点-1,查询第一个前缀和为x的位置。
单点修改直接做,查询时把区间补成$[1,2^{n}]$,由于树状数组每层只有左边那个块,直接套用线段树区间第k大的做法即可。
相当于我们把所有要填进来的数都填进来成为一个新序列,那么$qry(x)$就是当前x这个位置对应的新序列中的下标。
注意到下标和填进来了什么没关系,所以我们对每个询问预处理出它那一行的$qry(x)$和最后一列的$qry(y)$,然后直接拿vector填数就行了。
复杂度$O(q\log{n})$,细节有点多,考场上还是写80分吧。
套路:
- 一个复杂的操作$\rightarrow$拆成多个简单的小操作之和。
- 单点删除$\rightarrow$树状数组维护。
代码:
#include<bits/stdc++.h> #define maxn 2000005 #define maxm 500005 #define inf 0x7fffffff #define ll long long #define rint register ll #define debug(x) cerr<<#x<<": "<<x<<endl #define fgx cerr<<"--------------"<<endl #define dgx cerr<<"=============="<<endl using namespace std; struct Ques{ll x,y,rx,ry;}Q[maxn]; vector<ll> ins[maxn],vc[maxn]; inline ll read(){ ll x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } struct BIT{ ll C[maxn]; inline void clear(){memset(C,0,sizeof(C));} inline ll lowbit(ll x){return x&(-x);} inline void add(ll x,ll y,ll n){for(ll i=x;i<=n;i+=lowbit(i))C[i]+=y;} inline ll qry(ll x,ll n){ll l=1,r=n;while(l<r){ll mid=l+r>>1;if(C[mid]<x)l=mid+1,x-=C[mid];else r=mid;}return l;} }; int main(){ ll n=read(),m=read(),q=read(); for(ll i=1;i<=q;i++){ ll x=read(),y=read(); Q[i].x=x,Q[i].y=y,vc[Q[i].x].push_back(i); } BIT tp; tp.clear(); ll N=1; while(N<m+q) N<<=1; for(ll i=1;i<=N;i++) tp.add(i,1,N); for(ll i=1;i<=n;i++){ for(ll j=0;j<vc[i].size();j++){ ll id=vc[i][j]; if(Q[id].y==m) continue; Q[id].ry=tp.qry(Q[id].y,N),tp.add(Q[id].ry,-1,N); } for(ll j=0;j<vc[i].size();j++){ ll id=vc[i][j]; if(Q[id].y==m) continue; tp.add(Q[id].ry,1,N); } } tp.clear(); N=1; while(N<n+q) N<<=1; for(ll i=1;i<=N;i++) tp.add(i,1,N); for(ll i=1;i<=q;i++) Q[i].rx=tp.qry(Q[i].x,N),tp.add(Q[i].rx,-1,N); for(ll i=1;i<=n;i++) ins[n+1].push_back(i*m); for(ll i=1;i<=q;i++){ if(Q[i].y==m){ printf("%lld\n",ins[n+1][Q[i].rx-1]); ins[n+1].push_back(ins[n+1][Q[i].rx-1]); } else{ if(Q[i].ry>m-1){ printf("%lld\n",ins[Q[i].x][Q[i].ry-m]); ins[n+1].push_back(ins[Q[i].x][Q[i].ry-m]); } else{ printf("%lld\n",(Q[i].x-1)*m+Q[i].ry); ins[n+1].push_back((Q[i].x-1)*m+Q[i].ry); } ins[Q[i].x].push_back(ins[n+1][Q[i].rx-1]); } } return 0; }