hdu 2711 Lost Cows(单点更新)

题目链接  : hdu 2711 Lost Cows

题目大意:有n头牛,编号从1到n,排成n行,现在它们的顺序打乱了,每一头牛站一行,现在只知道在它前面几行中比它编号小的头数,站在第一行的牛前面没有东西,所以输入数据从第二行开始,让你求牛此时的序列;

解析:比如 输入序列为 1 2 1 0 ,从左往右分别表示比第二行编号小的牛有1头,比第三行小的有2头....比最后一行小的有0头,我们可以从后往前看,比最后一行小的牛有0头,说明最后一行牛的标号为1,因为1是最小的,假设我们有5头牛,比最后一头小的为2,则编号为3,由此可见,若比最后一头小的数有m,则最后一头的编号始终为m+1,然后再看倒数第二行,都是一样的道理。

这题用线段数维护区间内数的个数,开始与区间长度相同,后来每次从最后往前更新,对应叶子节点的区间值变为0,可以说是查询区间第k大;


#include<iostream>
#include<cstdio>
#define maxn 11111
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
using namespace std;
int sum[maxn<<2];
int L[maxn],x[maxn];
int cnt;
void pushup(int rt)
{
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];  //线段树维护的是区间的和,区间的和代表有几个数,更新查询时可以判断第几大
}
void build(int l,int r,int rt)
{
    if(l==r)
    {
        sum[rt]=(r-l+1); //区间初始化为区间的长度
        return;
    }
    int m=(l+r)>>1;
    build(lson);
    build(rson);
    pushup(rt);    //向上更新
}
void update(int p,int l,int r,int rt)
{
    if(l==r)
    {
        sum[rt]=0; //每次更新,这个数就被用过了,下次不再用,所以这个叶子节点赋值为一
        L[cnt++]=l;  //记录当前第p大的数为l
        return ;
    }
    int m=(l+r)>>1;
    if(p<=sum[rt<<1])    update(p,lson);  //区间记录的是区间内数字的个数,p表示第p大的数,也就是p个数,若p比左儿子区间的个数少,说明这个数在做儿子里
    else update(p-sum[rt<<1],rson);  //否则要减去左儿子的个数,在右儿子里找p-sum[rt<<1]大的数
    pushup(rt);  //向上更新
}
int query(int l,int r,int rt)
{
    if(l==r)  return l;  //这次询问的目的在于找到排在第一行的数,因为他前面不存在数,也就没有比它小的的数,所以该叶子节点始终没有被更新,值始终为1,只要找到这个一就行
    int m=(l+r)>>1;
    int ret=0;
    if(sum[rt<<1]==1)  ret=query(lson);  //左儿子区间等于1,往左儿子里查找
    else ret=query(rson);  //否则往右儿子里查找
    return ret;
}
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
            cnt=0;
            fill(L,L+maxn,0);
            build(1,n,1);
           for(int i=0;i<n-1;i++)  scanf("%d",&x[i]);
           for(int i=n-2;i>=0;i--)  update(x[i]+1,1,n,1);  //从最后一行的开始更新
           printf("%d\n",query(1,n,1)); //询问第一行的标号
           for(int i=cnt-1;i>=0;i--)  printf("%d\n",L[i]);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值