题目意思:
有n个人排队,每个人有个pos值,和value值,表示他可以插到第pos个人的后面,输出最后的队形序列。
解题思路:
如果顺着插的话,要移动后面的队列,如果用链表的话,找到插入的位置很费时。
如果逆着考虑的话,当前的人插到恰好有pos个空位的最小的地方,就很简单。
用线段树维护区间内空位的个数,如果要求的空位数大于左边区间的空位数,则减去左边的空位数,再在右边查找。
总结:抽象出线段树维护的空间很重要。
代码:
//如果逆着推的话,就不用移动很多元素了,这是关键,
//用线段树维护区间的空位数,如果左边的空位数不够的话,选择右边(其中把左边占满),知道找到插入的位置
//线段树 抽象出维护的空间 很关键
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define lson u<<1, l, mid
#define rson u<<1|1, mid+1, r
const int LEN=200010;
int sum[LEN<<2];
int val[LEN], s[LEN][2];
void Build(int u, int l, int r)
{
sum[u]=r-l+1;//区间右值减区间左值就是这个区间包含的空位置数
cout << l << " " << r << endl; //cout << endl;
if( l==r )//到了线段树叶子节点就返回
return;
int mid=l+r>>1;
Build(lson);//遍历左孩子
Build(rson);//遍历右孩子
}
int Update(int pos, int u, int l, int r)
{
--sum[u];//先减和一样,因为子区间插入了一个人父区间空位子数肯定少了一个
if( l==r )//遍历到叶子节点就返回
return l;//返回插入的人的位置区间,因为左右相等,所以区间变成一个数
//二分选择查找
int mid=l+r>>1;
//如果插入人前面有sum[u<<1]个人,因为这些是空位置,这题的方法是从后往前插入,
//所以后来逆退插入人的位置后面的人影响不了前面人的插入,则插入到左边区间;
if( pos<=sum[u<<1] ) return Update(pos, lson);
//否则插入到右区间,因为左边有sum[u<<1]个空位置,减去这些就是这个人要找的插的位子
else return Update(pos-sum[u<<1], rson);
//--sum[u];
}
int main()
{
int n, i, j, pos;
while( scanf("%d", &n)!=EOF )
{
for(i=0; i<n; i++)
scanf("%d%d", &s[i][0], &s[i][1]);
Build(1, 1, n);
for(i=n-1; i>=0; i--)
{
pos=Update(s[i][0]+1, 1, 1, n);//返回值是插入人的位置
val[pos]=s[i][1];//把插入人的价值赋给这个位置;
//cout << pos << " " << val[pos] << " ";
}
//cout << endl;
for(i=1; i<n; i++)
printf("%d ", val[i]);
printf("%d\n", val[i]);
}
return 0;
}