线段树题目

HDU—2795 广告牌

(单点查询,单点更新)
题意:有一个h*w的广告牌,有很多1*w的小广告,贴广告的优先级是先上然后左,按顺序给一些广告,依次输出该广告被贴在第几行,如果不能贴的话,就输出-1
注意:因为题目中的h是10^8,这样的数组建树就非常大了,没有必要建到h,当h>n的时候,建到n就可以了。

分析,将a[i]表示第i行还剩下的位置,然后建树,那么线段树维护的是区间中的行剩的最多的位置。

        using namespace std;
        #define MAX 200005
        struct node{
            int l,r,maxn;
        };
        node d[MAX<<2];
        int h,w,n;
        void buildtree(int i,int l,int r)
        {
            d[i].l=l,d[i].r=r;
            d[i].maxn=w;//初始化,最大值都等于广告牌的宽度
            if(l==r) return ;
            int mid=(l+r)/2;
            buildtree(i<<1,l,mid);
            buildtree(i<<1|1,mid+1,r);
        }
        int ans;
        void Insert(int i,int wi)
        {
            if(wi>d[i].maxn)//如果比当前最大值还小的话,就说明插入不了了
                {
                    ans=-1;
                    return ;
                }
            if(d[i].l==d[i].r)//对的,最后查询哪一行,也是点查询
               {
                   d[i].maxn-=wi;
                   ans= d[i].l;//这里要注意,不是返回i
                   return ;
               }
            if (wi<=d[i<<1].maxn)
                Insert(i<<1,wi);
            else if(wi<=d[i<<1|1].maxn)
                Insert(i<<1|1,wi);
            d[i].maxn=max(d[i<<1].maxn,d[i<<1|1].maxn);//对值进行维护
        }
        int main()
        {
            while(scanf("%d %d %d",&h,&w,&n)!=EOF)
            {
                if(h>n)
                    h=n;
                buildtree(1,1,h);
                int wi;
                for(int i=1;i<=n;i++)
                {
                    ans=0;
                    scanf("%d",&wi);
                    Insert(1,wi);
                    printf("%d\n",ans);
                }
            }
            return 0;
        }
HDU 3308 LCIS

(单点更新,区间查询)
题意:
有两种操作, U A,B表示将A位置的值改成B,Q A B 表示查询区间A,B中最大连续递增序列
有1e5个数字,然后会操作1e5次。
线段树维护,最大连续递增长度,区间左端点的值,区间右端点的值;

using namespace std;
#define maxn 100005

int s[maxn];
int mlen[maxn<<2],lz[maxn<<2],rz[maxn<<2];

int dd(int i,int l,int r,int mid,int k)//k=1表示l,r是完整的,用于插入,//k=0,表示l,r不一定完整,用于查询;
{
    int len1 = 0;
     if(k&&mlen[i<<1]==(mid-l+1)) len1+=mid-l+1;
        else {
            for(int j=mid;j>=l;j--){//这里l不要...和1弄混...
                if(s[j]<s[j+1]) len1++;
                else break;
            }
        }
        if(k&&mlen[i<<1|1]==(r-mid)) len1+=r-mid;
        else{
        for(int j = mid+1;j<=r;j++){
            if(s[j]>s[j-1]) len1++;
            else break;
            }
        }
        return len1;
}
void Insert(int i,int l,int r,int id,int x)
{
    if(l==r){
        lz[i]=rz[i]=x;mlen[i]=1;
        s[l]=x;
        return;
    }
    int mid = (l+r)>>1;
    if(id<=mid) Insert(i<<1,l,mid,id,x);
    else Insert(i<<1|1,mid+1,r,id,x);
    if(rz[i<<1]<lz[i<<1|1]){
        int len1 = dd(i,l,r,mid,1);
        mlen[i]=max(len1,max(mlen[i<<1],mlen[i<<1|1]));
    }
    else mlen[i]=max(mlen[i<<1],mlen[i<<1|1]);
    lz[i]=lz[i<<1];rz[i]=rz[i<<1|1];
}
int Query(int i,int l,int r,int L,int R)
{
    if(l==L&&r==R){
        return mlen[i];
    }
    int mid = (l+r)>>1;
    if(R<=mid) return Query(i<<1,l,mid,L,R);
    else if(L>mid) return Query(i<<1|1,mid+1,r,L,R);
    else{
        int len1=Query(i<<1,l,mid,L,mid);
        int len2=Query(i<<1|1,mid+1,r,mid+1,R);
        int len3=dd(i,L,R,mid,0);
        return max(len1,max(len2,len3));
    }
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n,m,j,k;
        char str[5];
        scanf("%d %d",&n,&m);
        for(int i=1;i<=n;i++){
            scanf("%d",&s[i]);
            Insert(1,1,n,i,s[i]);
        }
        while(m--)
        {
            scanf("%s %d %d",str,&j,&k);
            if(str[0]=='U'){
                Insert(1,1,n,j+1,k);
            }
            if(str[0]=='Q')
                printf("%d\n",Query(1,1,n,j+1,k+1));
        }
    }
    return 0;
}
codeforces 739C Alyona and towers

(区间更新)参考博客
题意:n个数,m个操作,每次操作会有x,y,z三个数,表示区间x,y中的数都增加z,每次操作后输出整个区间中最长山形序列
山形序列:形似a[i] < a[i+1] < a[i+2] < … < a[k] > a[k+1]>a[k+2]>…

开始想复杂了…
这个题还是很巧妙的.
先对数组进行差分,可以将区间修改转化成点修改。因为差分之后,一个区间[l,r]被修改了,只会影响a[l]-a[l]和a[r+1]-a[r]
然后因为差分之后,线段树的维护也就方便了很多

线段树维护5个值,分别是
ans:最长的山形序列长度
lans:从左端点开始的山形序列的长度
rans:从右端点开始的山形序列的长度
ldown:从左端点开始的严格递减的长度
rup:从右端点开始的严格递减的长度

维护步骤:…看代码吧…画画图
还是简单讲一讲,
1.父节点的ldown=左孩子的​ldown,如果左孩子的ldown=左孩子的区间长度,那么父节点的ldown = 左孩子的ldown+右孩子的ldown
2.父节点的rup = 右孩子的rup,如果右孩子的rup= 右孩子的区间长度,那么父节点的rup = 右孩子的rup+左孩子的rup
3.父节点的lans = 左孩子lans,如果左孩子的lans=左孩子的区间长度,则(如果左孩子的rup=左孩子的区间长度,那么父节点的lans=左孩子的lans+max(右孩子的lans,右孩子的ldown)),否则父节点的lans = 左孩子的lans+右孩子的ldown;)
4.父节点的rans = 右孩子的rans,如果右孩子的rans = 右孩子的区间长度,则(如果右孩子的ldwom = 右孩子的区间长度,那么父节点的rans = 右孩子的rans+max(左孩子的rans,左孩子的rup),否则父节点的lans = 右孩子的rans + 左孩子的rup);
5.父节点的ans = max(父节点的lans,父节点的rans,左孩子的ans,右孩子的ans,以及中间的山形长度);

using namespace std ;

#define ll long long
const int maxn=300000 + 10;
int ans[maxn<<2],lans[maxn<<2],rans[maxn<<2],ldown[maxn<<2],rup[maxn<<2];
ll a[maxn] , b[maxn] ;
int n , m ;
void update(int i,int l,int r,int mid)
{
    ldown[i]=ldown[i<<1];
    if(ldown[i<<1]==(mid-l+1))
        ldown[i]+=ldown[i<<1|1];
    rup[i] = rup[i<<1|1];
    if(rup[i<<1|1]==(r-mid))
        rup[i]+=rup[i<<1];
    lans[i]=lans[i<<1];
    if(lans[i<<1]==(mid-l+1)){
        if(rup[i<<1]==(mid-l+1)) lans[i]+=max(ldown[i<<1|1],lans[i<<1|1]);
        else lans[i]+=ldown[i<<1|1];
    }
    rans[i] = rans[i<<1|1];
    if(rans[i<<1|1]==(r-mid)){
        if(ldown[i<<1|1]==(r-mid)) rans[i]+=max(rans[i<<1],rup[i<<1]);
        else rans[i]+=rup[i<<1];
    }
    ans[i]=max(lans[i],rans[i]);
    ans[i]=max(ans[i],max(ans[i<<1],ans[i<<1|1]));
    ans[i]=max(ans[i],rans[i<<1]+(rup[i<<1]? max(ldown[i<<1|1],lans[i<<1|1]):ldown[i<<1|1]));
    ans[i]=max(ans[i],lans[i<<1|1]+((ldown[i<<1|1])?max(rup[i<<1],rans[i<<1]):rup[i<<1]));
}
void Build(int i,int l,int r)
{
    if(l==r){
        if(b[l]<0) ldown[i]=1;
        else if(b[l]>0) rup[i]=1;
        if(b[l]!=0) lans[i]=rans[i]=ans[i]=1;
        return;
    }
    int mid = (l+r)>>1;
    Build(i<<1,l,mid);
    Build(i<<1|1,mid+1,r);
    update(i,l,r,mid);
}
void Modify(int i,int l,int r,int id,int x){
    if(l==r){
        lans[i]=rans[i]=ans[i]=rup[i]=ldown[i]=0;
        b[l]+=x;
        if(b[l]<0) ldown[i]=1;
        else if(b[l]>0) rup[i]=1;
        if(b[l]!=0) lans[i]=rans[i]=ans[i]=1;
        return;
    }
    int mid = (l+r)>>1;
    if(id<=mid) Modify(i<<1,l,mid,id,x);
    else Modify(i<<1|1,mid+1,r,id,x);
    update(i,l,r,mid);
}
int main() {
    scanf( "%d" , &n ) ;
    for (int i = 1 ; i <= n ; i ++ ) cin >> a[i] ;
    a[0] = a[1] ;
    for (int i = 1 ; i <= n ; i ++ ) b[i] = a[i] - a[i-1] ;
    Build( 1 , 1 , n ) ;
    scanf( "%d" , &m ) ;
    for (int i = 1 ; i <= m ; i ++ ) {
        int l , r , del ;
        scanf( "%d%d%d" , &l , &r , &del ) ;
        if ( l > 1 ) Modify( 1 , 1 , n , l , del ) ;
        if ( r < n ) Modify( 1 , 1 , n , r + 1 , -del ) ;
        printf( "%d\n" , ans[1]+ 1 ) ;
    }
    return 0 ;
}
HDU 5493 Queue

题意:
有一个队伍,每个人有独一的高度,打散之后,她们只记得前面或者后面比自己高的人数k,给每个人的高度和k,问是否存在符合所有人条件的序列,并输出字典序最小的高度序列,不能的话输出impossible

分析:因为每个人只记得自己前面或者后面比自己高的人数,那么比自己矮的就没什么影响;需要来确定每个人的位置,那么需要知道一个区间中位置占用情况,那么就需要用到线段树了。
线段树维护的是区间剩余的位置
因为需要字典序最小,那么先按照身高从小到大排序,按顺序放入,对于当前身高a[i].h来说,还有c个空位,那么肯定是尽可能放在前面,因为后面的身高都会大于他,就不是最小字典序了。所以考虑一下是前面高人是a[i].k,还是后面高人是a[i].k

using namespace std;
#define ll long long
#define mem(a,b) memset(a,b,sizeof(a))

const int maxn = 1e5+10;
struct node{
    int h,k;
}a[maxn];
int v[maxn<<2];//应该维护剩余的
bool cmp(node a1,node a2){
    return a1.h<a2.h;
}
int ans[maxn];
void sert(int i,int l,int r,int x,int id){
    if(l==r){
        ans[l]=a[id].h;
        v[i]=0;
        return;
    }
    int mid = (l+r)>>1;
    if(v[i<<1]>x) sert(i<<1,l,mid,x,id);
    else sert(i<<1|1,mid+1,r,x-v[i<<1],id);
    v[i]=v[i<<1]+v[i<<1|1];
}
void build(int i,int l,int r){
    if(l==r){
        v[i]=1;return;
    }
    int mid = (l+r)>>1;
    build(i<<1,l,mid);
    build(i<<1|1,mid+1,r);
    v[i]=v[i<<1]+v[i<<1|1];
}
int main()
{
    int T,k=0;
    scanf("%d",&T);
    while(T--){
        mem(v,0);mem(ans,0);mem(a,0);
        int n,flag=0;
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%d %d",&a[i].h,&a[i].k);
        }
        sort(a+1,a+n+1,cmp);
        build(1,1,n);
        for(int i=1;i<=n;i++){
            if(a[i].k>(n-i)){flag=1;break;}
            int p = min(a[i].k,n-i-a[i].k);
            sert(1,1,n,p,i);
        }
        printf("Case #%d:",++k);
        if(flag)printf(" impossible\n");
        else {
            for(int i=1;i<=n;i++)
                printf(" %d",ans[i]);
            printf("\n");
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值