正题
题目链接:https://www.luogu.com.cn/problem/P3960
题目大意
n∗mn*mn∗m的队列,起初站在第(i,j)(i,j)(i,j)位置的人编号是(i−1)∗n+j(i-1)*n+j(i−1)∗n+j。然后每次选择一个人出队后所有人向左补齐后所有人向前补齐,然后刚刚出列的那个人入队。
求每次出列的人的编号。
解题思路
我们发现每次要补齐的就是出队那一行的和最后一列的。
首先考虑如何维护最后一列,我们要求每次删除一个数并加入一个到队尾。用权值线段树可以维护。
然后考虑对于每一行,我们分为两种情况,一种是本来在队列里的,一种是新加进来的。新加进来的不难维护,也是每次删除一个并且加入一个。对于原来就在队列里的,我们显然不能开n∗mn*mn∗m的线段树,正男♂难则反,权值线段树的权值表示这个范围内已经出队了的人数即可。
然后动态开点,时间复杂度O(nlogn)O(n\log n)O(nlogn)
codecodecode
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define p(x,y) (((x)-1)*m+(y))
using namespace std;
const ll N=1e6+10;
ll n,m,q,tot,rt[N],siz[N],ne[N];
struct Seq_Tree1{ll cnt,w[N<<4],ls[N<<4],rs[N<<4];ll Find(ll &x,ll l,ll r,ll val){if(!x)x=++cnt;if(l==r){w[x]++;return l;}ll mid=(l+r)>>1,ans;if(mid-w[ls[x]]>=val)ans=Find(ls[x],l,mid,val);else ans=Find(rs[x],mid+1,r,val+w[ls[x]]);w[x]=w[ls[x]]+w[rs[x]];return ans;}
}T1;
struct Seq_Tree2{ll cnt,w[N<<4],ls[N<<4],rs[N<<4],a[N<<4];void Insert(ll &x,ll l,ll r,ll pos,ll val){if(!x)x=++cnt;if(l==r){w[x]++;a[x]=val;return;}ll mid=(l+r)>>1;if(pos<=mid)Insert(ls[x],l,mid,pos,val);else Insert(rs[x],mid+1,r,pos,val);w[x]=w[ls[x]]+w[rs[x]];return;}ll Find(ll &x,ll l,ll r,ll val){if(!x)x=++cnt;if(l==r){w[x]--;return a[x];}ll mid=(l+r)>>1,ans;if(w[ls[x]]>=val)ans=Find(ls[x],l,mid,val);else ans=Find(rs[x],mid+1,r,val-w[ls[x]]);w[x]=w[ls[x]]+w[rs[x]];return ans;}
}T2;
int main()
{//freopen("P3960_7.in","r",stdin);scanf("%lld%lld%lld",&n,&m,&q);tot=n;for(ll i=1;i<=n;i++)T2.Insert(rt[0],1,n+q,i,p(i,m)),siz[i]=m-1;for(ll i=1;i<=q;i++){if(i==469-10)i++,i--;ll x,y;scanf("%lld%lld",&x,&y);if(y==m){ll k=T2.Find(rt[0],1,n+q,x);printf("%lld\n",k);T2.Insert(rt[0],1,n+q,++tot,k);}else if(y<=siz[x]){ll k=T1.Find(rt[x],1,m,y);printf("%lld\n",p(x,k));ll w=T2.Find(rt[0],1,n+q,x);T2.Insert(rt[x+n],1,q,++ne[x],w);T2.Insert(rt[0],1,n+q,++tot,p(x,k));siz[x]--;}else{ll k=T2.Find(rt[x+n],1,q,y-siz[x]);printf("%lld\n",k);ll w=T2.Find(rt[0],1,n+q,x);T2.Insert(rt[x+n],1,q,++ne[x],w);T2.Insert(rt[0],1,n+q,++tot,k);}}return 0;
}