题解:
我们发现其实就是维护n+1个序列,支持查找和删除第x个元素,以及在最后添加元素
前n个序列维护每一行的前m-1个元素,最后一个序列维护最后一列的元素
但是这样的话需要建n+1颗线段树,无法承受
但是可以发现一开始线段树中的元素是满的,并且一开始的元素编号十分有规律,可以直接计算
那么我们一开始就直接把所有线段树当作满的,用动态开点每次删点,后面加进来的点用vector储存,
当删除(x,y)时
如果y =m那么只要操作最后一列,如果是原来最后一列的元素,就直接计算,否则从vector中提取
如果y!=m那么先对第x行进行一次操作,转化成删除(x,m)位置的元素,然后再对最后一列进行操作
因为每次操作最多会加入两个元素,所以最多只会有O(2q)的元素储存在vector中
时间复杂度O(nlogn),空间复杂度O(nlogn)
标程:
//sz[i]表示i的子树中空格(已经被取掉的格子)的个数
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=300003,M=10000003;
int n,m,q,x,y,sz[M],cnt,le[M],ri[M],mx,rt[N];
vector<ll>G[N];
ll id;
int read(){
char c;int x=0,f=1;
do{c=getchar();if(c=='-')f=-1;}while(c<48||c>57);
do x=(x<<1)+(x<<3)+(c^48),c=getchar();while(c>=48&&c<=57);
return f*x;
}
int query(int l,int r,int &p,int x){
if (l==r) return l;
int mid=l+r>>1,h=mid-l+1-sz[le[p]];
if (x<=h) return query(l,mid,le[p],x);
return query(mid+1,r,ri[p],x-h);
}
void del(int l,int r,int &p,int x){
if (!p) p=++cnt;
sz[p]++;
if (l==r) return;
int mid=l+r>>1;
if (x<=mid) del(l,mid,le[p],x);
else del(mid+1,r,ri[p],x);
}
ll del_line(int x,int y){
int pos=query(1,mx,rt[x],y);
del(1,mx,rt[x],pos);
return pos<m?1ll*(x-1)*m+pos:G[x][pos-m];
}
ll del_row(int x){
int pos=query(1,mx,rt[n+1],x);
del(1,mx,rt[n+1],pos);
return pos<=n?1ll*(pos-1)*m+m:G[n+1][pos-n-1];
}
int main(){
n=read();m=read();q=read();
mx=max(n,m)+q;
while (q--){
x=read();y=read();
if (y==m){
id=del_row(x);
G[n+1].push_back(id);
printf("%lld\n",id);
}else{
id=del_line(x,y);
G[n+1].push_back(id);
printf("%lld\n",id);
id=del_row(x);
G[x].push_back(id);
}
}
}