1. 程式人生 > >Buy Tickets POJ

Buy Tickets POJ

題意

有N個人排名買票,現在給出每個人要插入的位置pos(0<=pos<=N-1)以及他的價值val。在插入N個人後,會構成一個新序列。現在讓你按順序 輸出新序列中每個位置pos對應的價值val

思路

最後一次插入某個位置的人,他的位置不會再改變,所以我們應該從後往前插入。用sum記錄所在空間剩餘的位置。對於插入在pos位置的人他前邊一定要有pos-1(1~n 建樹)個剩餘位置。(0~n 建樹則為 pos個剩餘位置)

訪問結點時,如果左子樹的剩餘位置大於或等於 pos 就訪問左子樹(sum[rt<<1] >= pos),否則就訪問右子樹。訪問右子樹時需要用pos減去左子樹的剩餘位置( pos-sum[rt<<1] ),即整個線段樹的第pos個空位(空葉子結點),也是在右兒子那的第pos-sum[rt<<1]個空位。

#include<cstdio>
const int maxn=200000;
int sum[maxn<<2],pos[maxn<<2],val[maxn<<2],ans[maxn<<2];  //ans儲存插入的值
void pushup(int rt)
{
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void bulid(int l,int r,int rt)
{
    if(l==r)
    {
        sum[rt]=1;    //初始化每個葉子結點剩餘位置為 1
        return ;
    }
    int m=(l+r)>>1;
    bulid(l,m,rt<<1);
    bulid(m+1,r,rt<<1|1);
    pushup(rt);
}
void update(int p,int v,int l,int r,int rt)
{
    if(l==r)
    {
        ans[rt]=v;
        sum[rt]--;
        return ;
    }
    int m=(l+r)>>1;
    if(sum[rt<<1]>=p)     //判斷插入左子樹還是右子樹
        update(p,v,l,m,rt<<1);
    else
        update(p-sum[rt<<1],v,m+1,r,rt<<1|1);
    pushup(rt);
}
void print(int l,int r,int rt)
{
    if(l==r)
    {
        printf("%d ",ans[rt]);
        return ;
    }
    int m=(l+r)>>1;
    print(l,m,rt<<1);
    print(m+1,r,rt<<1|1);
}
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        bulid(1,n,1);
        for(int i=0;i<n;i++)
            scanf("%d%d",&pos[i],&val[i]);
        for(int i=n-1;i>=0;i--)
            update(pos[i]+1,val[i],1,n,1);
        print(1,n,1);
        printf("\n");
    }
    return 0;
}

參考部落格