题面
分析
知名数据结构题。
今天拿给我做我还是会毫不犹豫地敲个50的暴力吧,因为细节其实是很多的。
考虑维护n+1棵线段树,前n棵维护n排的m-1个人,最后一棵维护最后一列的n个人
每次坐标为(x,y)的一个人离开队伍,相当于把这个人取出来放到最后一列的最后一个位置。
然后就这样就完了吗?原来第x行的最后一个位置,这时应该成为了倒数第二个位置,换言之,它本来不在第x棵线段树的维护范围内,现在它进入了,不能忘记把它加进去。
显然数组开不下,考虑动态开点,初始每棵树仅一个结点,其权值表示有多少个点。在我们查询或者插入需要访问用到一个结点时,再加入这个结点,并给它赋上信息。
代码
- #include<bits/stdc++.h>
- using namespace std;
- #define M 300030
- #define N 8000080
- #define mid (l+r>>1)
- #define ll long long
- int n,m,q,w,cnt,now;
- int root[M],t[N],lc[N],rc[N],pos[N];
- ll out,id[N];
- inline int cal(int l,int r)
- {//查询这段区间的结点数
- if(now<=n)
- {
- if(r<m)return r-l+1;
- else if(l<m) return m-1-l+1;
- else return 0;
- }
- else
- {
- if(r<=n)return r-l+1;
- else if(l<=n) return n-l+1;
- else return 0;
- }
- }
- inline void query(int &p,int l,int r)
- {
- if(!p)p=++cnt,t[p]=cal(l,r);
- if(l==r)
- {
- t[p]=0;
- if(!id[p])out=(now<=n)?(ll)(now-1)*(ll)m+l:(ll)m*(ll)l;
- else out=id[p];
- return;
- }
- else
- {
- t[p]--;//删除一个结点
- int left=lc[p]?t[lc[p]]:cal(l,mid);//个数是否达到要查的数量
- if(w<=left)query(lc[p],l,mid);//没达到,往左找
- else w-=left,query(rc[p],mid+1,r);//超过了,在右边找
- }
- }
- inline void update(int &p,int l,int r)
- {
- if(!p)p=++cnt,t[p]=cal(l,r);
- if(l==r){t[p]=1;id[p]=out;return ;}
- ++t[p];//加入一个结点
- if(w<=mid)update(lc[p],l,mid);
- else update(rc[p],mid+1,r);
- }
- int main()
- {
- scanf("%d%d%d",&n,&m,&q);
- for(int i=1;i<=n;i++)root[i]=++cnt,t[i]=m-1;
- root[n+1]=++cnt;t[n+1]=n;
- for(int i=1;i<=q;i++)
- {
- int a,b;ll tmp;
- scanf("%d%d",&a,&b);
- if(b<m)
- {
- now=a,w=b;
- query(root[now],1,m-1+q);//找到出队的点
- printf("%lld ",out);
- tmp=out,now=n+1;w=a;
- query(root[now],1,n+q); //从最后一列中找x行的最后一个点
- now=a,pos[now]++,w=m-1+pos[now];
- update(root[now],1,m-1+q);//把它归入第x棵线段树管理范畴
- now=n+1,pos[now]++,w=n+pos[now],out=tmp;
- update(root[now],1,n+q);//把最后一列最后一个点更新为出队的点
- }
- else
- {
- now=n+1,w=a;
- query(root[now],1,n+q);//本来就在最后一列,省去维护x行最后一列的操作
- printf("%lld ",out);
- pos[now]++,w=n+pos[now];
- update(root[now],1,n+q);
- }
- }
- return 0;
- }