POJ 2828 Buy Tickets 又是线段树!

有N个人排队,每来一个人他一定会得到他当前他想要去的位置。意味着越后来的人越能站到自己的位置上。

非常朴素的想法是,从第n-1个人开始:如果这个人想要去的位置上没有人的话,就让他去,然后标记为有人;如果他想要去的位置已经被标记为有人了,他就试图去下一个位置,如果还有人,继续寻找下一个位置(即++),直到找到一个空位为止。这样一定能保证答案是对的无疑。

但是对于题目所给的数据,n<=200000,最坏的情况就是 数据给 20W个0,这样的话用上面的方法无疑是超时的,因为时间已经超过 n^2;

现在就是考虑如何优化时间。

对于每个人都必须遍历一边的n是无法省去的,所以最好的算法应该就是nlog(n)了。

可以发现,从第n-1个人开始,当第n-1个人已经确定位置后,在考虑第n-2、n-3个人时,已经不再需要n-1这个人的信息了。所以每确定一个,就把这个人从队列里删除、更新队列长度。

这时候往线段树这边靠也是理所当然的了,但是我个人觉得要把用线段树出符合本题的节奏来也不是那么容易的。

深刻理解:线段树是一颗二叉树,对于根区间为[0,n-1] 的线段树,那么它的叶子节点就有n个,并且是按照0,1,2···n-1 从左右到右排列的。这是一个很重要的性质,很多题目用线段树来解都要用到这个性质,这题也是。

总的来说还是很巧妙的,可以仔细在代码中体会。

<pre name="code" class="cpp">#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
#define maxn 200005
struct node
{
    int left,right;
    int value;
}seg[maxn*4];

struct node2
{
    int pos,val;
}a[maxn];

void build(int i,int l,int r)
{
    seg[i].left=l;
    seg[i].right=r;
    seg[i].value=r-l+1;
    if(l==r)   return ;
    build(i<<1,l,(l+r)>>1);
    build(i<<1|1,((l+r)>>1)+1,r);
}

int query(int i,int pos)
{
    if(seg[i].left==seg[i].right)
    {
        seg[i].value=0;
        return seg[i].left;
    }
    int pos1;
    if(pos <= seg[i<<1].value)
        pos1=query(i<<1,pos);
    else
        pos1=query(i<<1|1,pos-seg[i<<1].value);

    seg[i].value=seg[i<<1].value+seg[i<<1|1].value;

    return pos1;
}


int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        build(1,0,n-1);
        for(int i=0;i<n;i++)
            scanf("%d%d",&a[i].pos,&a[i].val);
        int res[maxn];
        for(int i=n-1;i>=0;i--)
        {
            int pos=query(1,a[i].pos+1);
            res[pos]=a[i].val;
        }
        for(int i=0;i<n;i++)
            printf("%d%c",res[i],i==n-1?'\n':' ');
    }
    return 0;
}



你可能感兴趣的:(POJ 2828 Buy Tickets 又是线段树!)