思路来源: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;
}

 

posted on 2017-11-30 23:50  湮灭之瞳  阅读(64)  评论(0)    收藏  举报