Super Mario HDU - 4417 (主席树-离线处理)

8 篇文章 0 订阅
2 篇文章 0 订阅

Super Mario

 HDU - 4417 

 题目要求给出一个区间,求出区间【l,r】之内小于等于给定H的数的个数. 我们可以类比求区间第k大的方法去求区间小于等于H的,

有一个技巧就是先将H读入,将H和原数据一起离散化,但是建树的时候依然只建n棵树,最后每次求一下[0-r]  [0-l-1]

做差就可以了(注意题目区间是【0,n-1】)。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
#define N 100000

int a[N*4],b[N*4];
int sum[N*20],T[N*20];
int L[N*20],R[N*20];
int cnt=0;

void build(int &rt,int l,int r)
{
    rt=++cnt;
    sum[rt]=0;
    if(l==r)return ;
    int m=(l+r)>>1;
    build(L[rt],l,m);
    build(R[rt],m+1,r);
}

void update(int &rt,int pre,int l,int r,int x)
{
    rt=++cnt;
    L[rt]=L[pre],R[rt]=R[pre];
    sum[rt]=sum[pre]+1;
    if(l==r)return ;
    int m=(l+r)>>1;
    if(x<=m)update(L[rt],L[pre],l,m,x);
    else update(R[rt],R[pre],m+1,r,x);
}

int query(int rt,int l,int r,int k)
{
    int ans=0;
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(k<mid)
        {
            rt=L[rt];
            r=mid;
        }
        else
        {
            ans+=sum[L[rt]];
            rt=R[rt];
            l=mid+1;
        }
    }
    return ans;
}

int be[N],en[N],H[N];

int main()
{
    int t;
    scanf("%d",&t);
    for(int cas=1;cas<=t;cas++)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            b[i]=a[i];
        }
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d%d",&be[i],&en[i],&H[i]);
            b[i+n]=H[i];
        }
        sort(b+1,b+1+n+m);
        int len=unique(b+1,b+1+n+m)-b-1;
        cnt=0;
        build(T[0],1,len);
        for(int i=1;i<=n;i++)
        {
            int pos=lower_bound(b+1,b+1+len,a[i])-b;
            update(T[i],T[i-1],1,len,pos);
        }
        printf("Case %d:\n",cas);
        for(int i=1;i<=m;i++)
        {
            int p1=lower_bound(b+1,b+1+len,H[i])-b;
            int k1=query(T[en[i]+1],1,len,p1);
            int k2=query(T[be[i]],1,len,p1);
            printf("%d\n",k1-k2);
        }
    }
    return 0;
}

参考了大牛的题解发现可以用树状数组离线处理,具体思路是这样的。

对于区间里的每一个值,我们按照从小到大排序,然后我们按照位置依次插入

同样的,对于要查询的H我们也将它从小到大排序,这样我们就能保证,当查询到较大的H时

比H小的值都已经插进树状数组里了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 100005
#define lowb(x) x&-x;
struct node
{
    int k,id;
}a[N];

struct node1
{
    int l,r,h,id;
}H[N];
int L[N],R[N];
int C[N];
int ans[N];
bool cmp(node x,node y)
{
    return x.k<y.k;
}

bool cmp1(node1 x,node1 y)
{
    return x.h<y.h;
}
void add(int pos)
{
    while(pos<=N)
    {
        C[pos]+=1;
        pos+=lowb(pos);
    }
}

int getsum(int x)
{
   int ans=0;
   while(x)
   {
       ans+=C[x];
       x-=lowb(x);
   }
   return ans;
}

void solve(int m,int n)
{
    int top=1;
    for(int i=1;i<=m;i++)
    {
        while(top<=n&&a[top].k<=H[i].h)
        {
            add(a[top].id);
            top++;
        }
//        printf("%d %d %d\n",H[i].l,H[i].r,H[i].h);
//        printf("%d %d\n",getsum(H[i].l-1),getsum(H[i].r));
        ans[H[i].id]=getsum(H[i].r)-getsum(H[i].l-1);
    }
    for(int i=1;i<=m;i++)
    {
        printf("%d\n",ans[i]);
    }
}
int main()
{
     int t;
     cin>>t;
     for(int cas=1;cas<=t;cas++)
     {
         int n,m;
         memset(C,0,sizeof(C));

         scanf("%d%d",&n,&m);
         for(int i=1;i<=n;i++)
         {
             scanf("%d",&a[i].k);
             a[i].id=i;
         }
         sort(a+1,a+1+n,cmp);
         int x,y;
         for(int i=1;i<=m;i++)
         {
             scanf("%d%d%d",&x,&y,&H[i].h);
             H[i].l=x+1,H[i].r=y+1;
             H[i].id=i;
         }
         sort(H+1,H+1+m,cmp1);
         printf("Case %d:\n",cas);
         solve(m,n);
     }
}

补充:

学习了划分树之后,本题又有了另一种快速简单的解法。就是二分枚举区间第k大的值。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define N 100005
int t[20][N],tl[20][N];
int sor[N];

void build(int l,int r,int dep)
{
    if(l==r)return ;
    int m=(l+r)>>1;
    int same = m-l+1;
    for(int i=l;i<=r;i++)if(t[dep][i]<sor[m])same--;
    int ln=l;
    int rn=m+1;

    for(int i=l;i<=r;i++)
    {
        if(t[dep][i]<sor[m]||(t[dep][i]==sor[m]&&same>0))
        {
            t[dep+1][ln++]=t[dep][i];
            if(t[dep][i]==sor[m])same--;
            tl[dep][i]=tl[dep][l-1]+ln-l;
        }
        else
        {
            t[dep+1][rn++]=t[dep][i];
            tl[dep][i]=tl[dep][i-1];
        }
    }
    build(l,m,dep+1);
    build(m+1,r,dep+1);
}

int query(int l,int r,int ql,int qr,int k,int dep)
{
    if(l==r)return t[dep][l];
    int m=(l+r)>>1;
    int cnt=tl[dep][qr]-tl[dep][ql-1];
    if(cnt>=k)
    {
        int newl=l+tl[dep][ql-1]-tl[dep][l-1];
        int newr=newl+cnt-1;
        return query(l,m,newl,newr,k,dep+1);
    }
    else
    {
        int newr=qr+tl[dep][r]-tl[dep][qr];
        int newl=newr-(qr-ql-cnt);
        return query(m+1,r,newl,newr,k-cnt,dep+1);
    }
}

void solve(int n,int ql,int qr,int h)
{
    int l=1;
    int r=(qr-ql)+1;
    while(l<=r)
    {
        int m=(l+r)>>1;
        int tmp=query(1,n,ql,qr,m,0);
        if(tmp<=h)
        {
            l=m+1;
        }else r=m-1;
    }
    printf("%d\n",r);

}

int main()
{
    int tt;
    cin>>tt;
    int cas=1;
    while(tt--)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&t[0][i]);
            sor[i]=t[0][i];
        }
        sort(sor+1,sor+1+n);
        build(1,n,0);
        printf("Case %d:\n",cas++);
        while(m--)
        {
            int l,r,h;
            scanf("%d%d%d",&l,&r,&h);
            solve(n,l+1,r+1,h);
        }
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值