这个题目对一个初入noip的童鞋来说很烧脑呢,因为对线段树并不是很熟悉,所以在做这个题目之前需要先学习几个知识点:
线段树
权值线段树
主席树,可持久化线段树
可持久化数组
经过对以上几个知识点,尤其是权值线段树和主席树的学习,才能理解如何动态建立多棵线段树,如何去找区间第K大的值。
然后才能慢慢理解这道题的做法,很多巧妙的思想是自己无论如何也想不出来的。还是佩服这些烧脑的大仙们。。。
#include<bits/stdc++.h>
#define mid ((l+r)>>1)
using namespace std;
typedef long long ll;
const int N=3e5+5;
const int M=1e7+2;
ll n,m,q;
struct node{
int ls,rs; //存储左右子树根节点的编号
int sz; //存储的是当前的有效位数
ll val; //存储的是队员的编号,只对 l==r,叶子节点有效
}t[M];
int id,tot; //动态开点
int rt[N]; //存储n+1棵线段树的根节点编号
ll now; //当前操作的线段树编号
int cur[N]; //第i棵线段树当前要更新的点的编号
int up;
//如果有点还没有编号,说明当前没有人离队,这部分是完整的r-l+1个有效位
//我们就直接返回这个个数就行了
int get(int l,int r){
if(now==n+1){
if(r<=n) return r-l+1;
if(l<=n) return n-l+1;
return 0;
}
if(r<m) return r-l+1;
if(l<m) return m-l;
return 0;
}
//查询x的第c个数,离队
ll query(int &x,int l,int r,int c){
//编号
if(!x){
x=++tot;
t[x].sz=get(l,r);
if(l==r){
if(now==n+1) t[x].val=l*m;
else t[x].val=(now-1)*m+l;
}
}
//个数-1,离队
t[x].sz--;
if(l==r) return t[x].val;
if((!t[x].ls&&c<=get(l,mid))||c<=t[t[x].ls].sz) return query(t[x].ls,l,mid,c);
else{
if(!t[x].ls) c-=get(l,mid);
else c-=t[t[x].ls].sz;
return query(t[x].rs,mid+1,r,c);
}
}
//x入队 ,位置是to,编号是d
void upda(int &x,int l,int r,int to,ll d){
if(!x){
x=++tot;
t[x].sz=get(l,r);
if(l==r){
t[x].val=d;
}
}
//入队,个数+1
t[x].sz++;
if(l==r) return;
if(to<=mid) return upda(t[x].ls,l,mid,to,d);
else return upda(t[x].rs,mid+1,r,to,d);
}
int main()
{
scanf("%lld%lld%lld",&n,&m,&q);
int x,y;
ll ans;
up=max(n,m)+q;
while(q--){
scanf("%d%d",&x,&y);
//如果是第m列离队,则查询第n+1棵线段树的第x个数
if(y==m) now=n+1,ans=query(rt[now],1,up,x);
//否则,查询第x个线段树的第y个数
else now=x,ans=query(rt[now],1,up,y);
printf("%lld\n",ans);
//第n+1棵线段树增加一个数,在 n+(++cur[now]) 位置
//这个位置是新的有效位
now=n+1;
upda(rt[now],1,up,n+(++cur[now]),ans);
if(y!=m){
now=n+1;
//取出n+1线段树的第x个数
ans=query(rt[now],1,up,x);
now=x;
//放入第x个线段树的 m-1+(++cur[now] 位置
upda(rt[now],1,up,m-1+(++cur[now]),ans);
}
}
return 0;
}代码参考:https://www.cnblogs.com/Miracevin/p/9582412.html

有话要说...