CSU-ACM集训-模板-主席树

原hdu 2665 Kth number

题意

\(n\)个数和\(m\)次查询,每个查询包含区间\([x,y]\),求区间内第\(K\)大的数

思路

可持久化线段树,即主席树,第一次建立一个空的线段树,使用\(root\)下标表示访问第几次时间,数据离散化后。注意下标从1开始。
注意\(cnt\)可能是乱序的,但是\(root\)控制时间区间,即表示时间\(i\)的根节点为\(nodes[root[i]]\)\(nodes\)\(l\)\(r\)控制树的左右关系。

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <sstream>
#include<iomanip>
#define FOR(i,a,b) for(int i=a;i<b;i++)
#define FOR2(i,a,b) for(int i=a;i<=b;i++)
#define sync ios::sync_with_stdio(false);cin.tie(0) 
#define ll long long
#define MAXN 100010
using namespace std;
typedef struct{
    int l,r,sum;
}NODE;NODE nodes[20*MAXN];
int root[MAXN],a[MAXN],b[MAXN];
int n,m,cnt,p;

int getid(int x)
{//离散化 
    return lower_bound(b+1,b+1+p,x)-b;
}
void build(int l,int r,int &cur)
{//建立空树 
    nodes[cur].sum=nodes[cur].l=nodes[cur].r=0;
    cur=++cnt;
    if(l==r)return;
    int mid=(l+r)>>1;
    build(l,mid,nodes[cur].l);
    build(mid+1,r,nodes[cur].r);
}
void update(int l,int r,int &cur,int pre,int pos)
{
    cur=++cnt;//表示新开的节点 
    nodes[cur].l=nodes[pre].l;
    nodes[cur].r=nodes[pre].r;
    nodes[cur].sum=nodes[pre].sum+1;//记录该点该时间之前有多少的数字 
    if(l==r)return ;
    int mid=(l+r)>>1;//每次按照pos的值插入到1~p的范围中 
    if(pos<=mid)update(l,mid,nodes[cur].l,nodes[pre].l,pos);
    else update(mid+1,r,nodes[cur].r,nodes[pre].r,pos);
}
int query(int l,int r,int x,int y,int key)
{
    if(l==r)return l;
    int mid=(l+r)>>1;
    int sum=nodes[nodes[y].l].sum-nodes[nodes[x].l].sum;//时间区间内个数 
    if(key<=sum)//查找右区间 
        return query(l,mid,nodes[x].l,nodes[y].l,key);
    else//查找左区间 
        return query(mid+1,r,nodes[x].r,nodes[y].r,key-sum);
}
void start()
{
    int x,y,k;

    memset(root,0,sizeof(root));
    cnt=0;
    FOR2(i,1,n)
    {
        cin>>a[i];
        b[i]=a[i];
    }
    cnt=0;
    sort(b+1,b+n+1);//离散化 
    p=unique(b+1,b+n+1)-b-1;//数据范围 
    build(1,p,root[0]); //建立时间点为0 的空树 
    FOR2(i,1,n)//i表示时间 ,按时间插入a[i]元素到线段树 
        update(1,p,root[i],root[i-1],getid(a[i])); //按照a[i]在b[i]中的大小插入到不同位置 
    while(m--)
    {
        cin>>x>>y>>k;//k=y-x-k+2;第K大和第K小的差别 
        cout<<b[query(1,p,root[x-1],root[y],k)]<<endl;//区间第K大就是求 时间区间的第K大 
    }
}
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
    memset(root,0,sizeof(root));
        cin>>n>>m;
        start();
    }
}

原hdu 4348 To the moon

题意

区间和线段树的可持久化,典型板子题,C表示区间加,Q表示区间查询,H表示区间查询第t时间的值,B表示返回第t时间,之后无法在前进。

基本思路

首先,需要学会基本的懒节点标记的区间和线段树,然后改,具体看代码,就是加了一层root时间节点
数组一定要开到25W左右,20W以下会爆越界错!不能开太大(超过28W,像我用结构体没空间优化),就会爆内存T_T

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <sstream>
#include<iomanip>
#define FOR(i,a,b) for(int i=a;i<b;i++)
#define FOR2(i,a,b) for(int i=a;i<=b;i++)
#define sync ios::sync_with_stdio(false);cin.tie(0) 
#define ll long long
#define MAXN 100010
using namespace std;
typedef struct {
    int l,r;
    ll w,laz;
}NODE; NODE nodes[2500010];
int n,m,root[MAXN],cnt,x,y,d,t,now,num;
ll ans;

int build(int l,int r)
{
    int cur;
    cur=num++;
    nodes[cur].l=nodes[cur].r=nodes[cur].w=nodes[cur].laz=0;
    if(l==r)
    {
        cin>>nodes[cur].w;
        return cur;
    }
    int mid=(l+r)>>1;
    nodes[cur].l=build(l,mid);
    nodes[cur].r=build(mid+1,r);
    nodes[cur].w=nodes[nodes[cur].l].w+nodes[nodes[cur].r].w;//递归建树 
    return cur;//返回节点坐标 
}

int update(int pre,int x,int y,int l,int r,int d)
{
    int cur=num++;
    nodes[cur]=nodes[pre];
    nodes[cur].w+=d*(min(y,r)-max(x,l)+1);
    if(x<=l&&r<=y)
    {
        nodes[cur].laz+=d;//懒节点,建议使用返回值的函数 
        return cur;
    }
    int mid=(l+r)/2;
    if(x<=mid)
    {//左区间查询 
        nodes[cur].l=update(nodes[cur].l,x,y,l,mid,d);
    }
    if(y>mid)
    {//右区间查询 
        nodes[cur].r=update(nodes[cur].r,x,y,mid+1,r,d);
    }
    return cur;//返回节点坐标 
}

ll query(int cur,int x,int y,int l,int r)
{
    ll res=nodes[cur].laz*(min(y,r)-max(x,l)+1);//懒节点不用下传,这是返回函数的好处,不返回值的函数需要每次修改节点的w和子节点的laz标志 
    if(x<=l&&r<=y)
    {
        return nodes[cur].w;
    }
    int mid=(l+r)/2;
    if(x<=mid)res+=query(nodes[cur].l,x,y,l,mid);
    if(y>mid)res+=query(nodes[cur].r,x,y,mid+1,r);
    return res;
}

int main()
{
    bool flag=false;
    while(cin>>n>>m)
    {
        if(flag)cout<<endl;
        else flag=true;
        memset(root,0,sizeof(root));
        memset(nodes,0,sizeof(nodes));
        num=now=0;
        root[now]=build(1,n);
        while(m--)
        {
            char op;cin>>op;
            while(op=='\n')cin>>op;
            if(op=='C')
            {
                cin>>x>>y>>d;
                now++;//线段树时间坐标 
                root[now]=update(root[now-1],x,y,1,n,d);
            }
            if(op=='Q')
            {
                cin>>x>>y;
                cout<<query(root[now],x,y,1,n)<<endl;
            }
            if(op=='H')
            { 
                cin>>x>>y>>t;
                cout<<query(root[t],x,y,1,n)<<endl;
            }
            if(op=='B')
            {
                cin>>now;
            }
        }
    }
    return 0;
}

原hdu 6278 Just h-index

题意

与hdu 2665 类似,查找区间内第\(K\)小的数\(a_k\),使得满足\(a_k-1<=\){大于\(a_k-1\)的个数}

基本思路

二分查找第K小,即mid,离散化查询第K-1个数,使得该数满足条件 。关键在于二分

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
#include <queue>
#include <map>
#include <set>
#include <sstream>
#include<iomanip>
#define FOR(i,a,b) for(int i=a;i<b;i++)
#define FOR2(i,a,b) for(int i=a;i<=b;i++)
#define sync ios::sync_with_stdio(false);cin.tie(0) 
#define ll long long
#define MAXN 100010
using namespace std;
typedef struct{
    int l,r;
    ll w;
}NODE;NODE nodes[24*MAXN];
int root[24*MAXN],num,now,n,q,p;
ll arr[MAXN],b[MAXN];
int getid(ll x)
{
    return lower_bound(b,b+p,x)-b+1;
}
void build(int l,int r,int &cur)
{//建立空树 
    nodes[cur].l=nodes[cur].r=nodes[cur].w=0;
    num++;
    cur=num;
    if(l==r)return  ;
    int mid=(l+r)>>1;
    build(l,mid,nodes[cur].l);
    build(mid+1,r,nodes[cur].r);
}
void update(int l,int r,int &cur,int pre,int pos)
{
    num++;//建立新节点
    cur=num;
    nodes[cur].l=nodes[pre].l;
    nodes[cur].r=nodes[pre].r;
    nodes[cur].w=nodes[pre].w+1;
    if(l==r)return;
    int mid=(l+r)>>1;
    if(pos<=mid)update(l,mid,nodes[cur].l,nodes[pre].l,pos);
    else update(mid+1,r,nodes[cur].r,nodes[pre].r,pos);
}

int query(int l,int r,int x,int y,int key)
{
    if(l==r)return l;
    int mid=(l+r)>>1;
    int c=nodes[nodes[y].l].w-nodes[nodes[x].l].w;//区间个数 
    if(key<=c)return query(l,mid,nodes[x].l,nodes[y].l,key);//左区间
    else return query(mid+1,r,nodes[x].r,nodes[y].r,key-c);//右区间 
}
void start()
{
    p=num=now=0;
    
    FOR(i,0,n)
    {
        cin>>arr[i];
        b[i]=arr[i];
    }
    
    sort(b,b+n);
    p=unique(b,b+n)-b;//离散化 
    
    build(1,p,root[0]);
    FOR(i,0,n)update(1,p,root[i+1],root[i],getid(arr[i]));
    
    while(q--)
    {
        int x,y;cin>>x>>y;
        int l=1,r=y-x+1,rr=r,ans=1;//l r表示查询的区间大小,确定mid 
        while(l<=r)
        {
            int mid=(l+r)>>1;
            int t=query(1,p,root[x-1],root[y],mid);//求区间第mid大的数 
//          cout<<"mid="<<mid<<"t="<<t<<"b="<<b[t-1]<<endl;
            if(b[t-1]>=rr-mid+1) {
                ans=rr-mid+1;
                r=mid-1; 
            }
            else l=mid+1;
        }
        cout<<ans<<endl;
    }
}
int main()
{
    while(cin>>n>>q)
    {
        start();
    }
    return 0;
}

转载于:https://www.cnblogs.com/tldr/p/11246857.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值