题目

https://www.luogu.org/problemnew/show/P3960

思路

树状数组做法:

定义一行中原来的元素为 初始时这一行前m-1m−1个元素中,没有离队过的元素。

我们观察到对于本来就在这一行中的元素,我们可以直接算出它的值,而不用存储。

那么我们判断每一次询问是不是在本行的原来的元素中,如果是,直接判断掉。

那么每一行的“非原来的元素”有多少个呢?

我们不知道一行会有多少个,但是我们知道,所有行的这样的元素个数的总和不超过q个。

这启发了我们对于每一行,只对其“非原来的元素”开一个树状数组维护。

而对于“原来的元素”,我们直接离线预处理。

预处理时需要对所有的询问按照x_i为第一关键字,询问编号为第二关键字排序。

再对最后一列单独处理。

线段数做法:

我们开1/2棵线段树,维护每个区间实际存在的数字个数,这样就可以利用区间长度-sum[x]的方法找到区间的第k个元素,同时我们维护1/2个vector,表示添加到末尾的数字,如果查找到的pos是在实际范围以内,那么直接按照pos计算,否则我们在vector中按照下标查找,之后再更新vector和线段树即可。
但会MLE
对每一行和最后一列维护n+1棵线段树和n+1个vector,由于动态开点我们的内存能够控制在nlogn的级别,vector中的元素在2q级别,不会超出内存限制,剩下的就按照刚才那样进行就行了,注意如果y=m,就不用对第x行的线段树进行操作了,否则我们就先在第x行找到答案,然后在第n+1棵线段树中找到第x个元素并添加到x中,然后把ans添加到n+1中。

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int n,m,q,x,y,pos,tot,lim,rt[300005],ls[12000005],rs[12000005],sm[12000005];vector<ll>v[300005];
int query(int x,int l,int r,int v)
{
    if(l==r) return l;
    int mid=(l+r)>>1,tmp=mid-l+1-sm[ls[x]];
    if(v<=tmp) return query(ls[x],l,mid,v);
    return query(rs[x],mid+1,r,v-tmp);
}
void update(int &x,int l,int r,int p)
{
    if(!x) x=++tot;sm[x]++;
    if(l==r) return;int mid=(l+r)>>1;
    if(p<=mid) update(ls[x],l,mid,p);
    else update(rs[x],mid+1,r,p);
}
ll work1(int x,ll y)
{
    pos=query(rt[n+1],1,lim,x);update(rt[n+1],1,lim,pos);
    ll ans=pos<=n?1ll*pos*m:v[n+1][pos-n-1];
    return v[n+1].push_back(y?y:ans),ans;
}
ll work2(int x,int y)
{
    pos=query(rt[x],1,lim,y);update(rt[x],1,lim,pos);
    ll ans=pos<m?1ll*(x-1)*m+pos:v[x][pos-m];
    return v[x].push_back(work1(x,ans)),ans;
}
int main()
{
    scanf("%d%d%d",&n,&m,&q);lim=max(n,m)+q;
    for(;q--;printf("%lld\n",y==m?work1(x,0):work2(x,y))) scanf("%d%d",&x,&y);
}