题意:有n个人(每个人价值为val[i])依次从外面往队伍中插队,每次都会插在第pos[i]个位置,原本在pos[i]及其后面的人将会往后移一位,按最后队伍的顺序输出这些人价值。
思路:正着模拟显然超时,可以这样想,每次入队的人位置由两个因素影响,一来是他本身插队到的位置,第二个是后面的人插队使他往后移位。注意到最后差进来那个人位置一定不会改变,考虑到倒序插入队伍。现在我们观察插入到位置x的这个人,他前面应该已经插满了x个人,然后他后面还会有人插队。因此他应该预留x个空位给他前面的人,他自己占有第x+1个空位。插入这个人以后,更新每个点的空位数以便下一次插入时的查询,这些用线段树维护。
具体做法是:x[i]=1或0表示i点有一个空格或没有空格,那么如果我们用sum[i]代表前i个位置的空格总数,我们现在的任务就是找到一个最小的i,使得sum[i]=x+1。用线段树维护,二差查找。如果左子树上的空位数加上他的左边所有空位数之和大于等于x+1,则代表左子树上有点满足sum[i]=x+1,因此往左子树查找。否则往有子树查找。找到满足条件的点i后,使假想的x[i]=0,维护线段树。
#include<cstdio>
#define M ((L+R)>>1)
#define ls (o<<1)
#define rs (o<<1|1)
#define lson ls,L,M
#define rson rs,M+1,R
#define MAXN 200005
int val[MAXN],pos[MAXN],ans[MAXN],n,sumv[MAXN*3],s,v;
void pushup(int o) {sumv[o]=sumv[ls]+sumv[rs];}
void build(int o,int L,int R)
{
if(L==R) {sumv[o]=1;return;}
build(lson); build(rson);
pushup(o);
}
void query(int o,int L,int R,int sum)//sum用来记录根o左端空位数和
{
if(L==R){sumv[o]=0;ans[L]=v;return;}
sum+sumv[ls]>=s? query(lson,sum):query(rson,sum+sumv[ls]);
//如果左子树上的空位数加上他的左边所有空位数之和大于等于x+1则代表左子树上有点满足sum[i]=x+1,因此往左子树查找。否则往有子树查找。
pushup(o);
}
int main()
{
while(~scanf("%d",&n))
{
for(int i=0;i<n;++i) scanf("%d%d",&pos[i],&val[i]);
build(1,1,n);
for(int i=n-1;i>=0;--i)
{
v=val[i],s=pos[i]+1;
query(1,1,n,0);
}
printf("%d",ans[1]);
for(int i=2;i<=n;++i) printf(" %d",ans[i]);
puts("");
}
return 0;
}