主席树

可持久化线段树-主席树

可持久化线段树不是一棵完全二叉树,不能用层次序编号,直接记录每个节点的左右子节点,与trie的字符子针类似

可持久化线段树的空间复杂度为O(N+MlogN).

一般L,R,sum,num至少开25倍空间

结构图:(摘自<<算法竞赛进阶指南>>)

模板题

https://vjudge.net/problem/POJ-2104

题意:

询问区间第k小,不修改

每次查询就查询左子树的个数是否大于k

如果大于,呢么第k大肯定在左子树

否则去右子树查找,最后找到的(l==r),呢个坐标就是答案了,返回b[l].

ac:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define MAXN 3000005
using namespace std;
int a[MAXN],b[MAXN],root[MAXN];//原结点值(会离散化),离散化前的值,第i次的根
int L[MAXN],R[MAXN];//L[i]为第i个节点的左儿子,R[i]为第i的节点的右儿子
int sum[MAXN];//sum[i]为节点的所代表的区间的总个数,即i节点的值
int tot=0;

int build(int l,int r)//建立空树
{
    int now=++tot;
    sum[now]=0;
    if(l!=r)//不为叶子结点,继续递归
    {
        int mid=(l+r)>>1;
        L[now]=build(l,mid);
        R[now]=build(mid+1,r);
    }
    return now;
}
//每次插入一个点,即加入一条链(长度logn)
int update(int pre,int l,int r,int k)
{
    int now=++tot;
    sum[now]=sum[pre]+1;//新结点的个数为旧结点个数+1
    L[now]=L[pre],R[now]=R[pre];//新结点继承旧结点
    if(l!=r)//创建一条链
    {
        int mid=(l+r)>>1;
        if(k<=mid)
            L[now]=update(L[pre],l,mid,k);
        else
            R[now]=update(R[pre],mid+1,r,k);
    }
    return now;
}

int query(int x,int y,int l,int r,int k)
{
    if(l==r)
        return b[l];//返回l,l已经离散化,所以返回离散化后的b[l]
    int mid=(l+r)>>1;
    int num=sum[L[y]]-sum[L[x]];//左子树的个数
    if(num>=k)
        return query(L[x],L[y],l,mid,k);
    else
        return query(R[x],R[y],mid+1,r,k-num);//去右子树找第k-num(左子树个数)
}

int main()
{
    int n,m,l,r,k;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),b[i]=a[i];
    sort(b+1,b+1+n);
    int num=unique(b+1,b+n+1)-b-1;
    for(int i=1;i<=n;i++)
        a[i]=lower_bound(b+1,b+num+1,a[i])-b;
    root[0]=build(1,num);//建立一棵空树
    for(int i=1;i<=n;i++)
        root[i]=update(root[i-1],1,num,a[i]);//root[i]是在root[i-1]的基础上建立的
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&l,&r,&k);
        printf("%d\n",query(root[l-1],root[r],1,num,k));
    }
    return 0;
}

HDU - 4417

题意:

询问区间l~r中小于等于k的数目

解析:

查询<=k的,我们二分出第一个大于等于k的,如果他不等于k,就--,

如果是==0,就直接输出0,不能取搜0,因为最小是1会出现非法访问.

ac:

#include<bits/stdc++.h>
#define MAXN 3000005
using namespace std;
int a[MAXN],b[MAXN],root[MAXN];
int L[MAXN],R[MAXN];
int sum[MAXN];
int tot=0;

int build(int l,int r)
{
    int now=++tot;
    sum[now]=0;
    if(l!=r)
    {
        int mid=(l+r)>>1;
        L[now]=build(l,mid);
        R[now]=build(mid+1,r);
    }
    return now;
}

int update(int pre,int l,int r,int k)
{
    int now=++tot;
    sum[now]=sum[pre]+1;
    L[now]=L[pre],R[now]=R[pre];
    if(l!=r)
    {
        int mid=(l+r)>>1;
        if(k<=mid)
            L[now]=update(L[pre],l,mid,k);
        else
            R[now]=update(R[pre],mid+1,r,k);
    }
    return now;
}

int query(int x,int y,int l,int r,int k)
{
    if(sum[y]-sum[x]==0) return 0;
    if(l==r)
    return sum[y]-sum[x];
    int mid=(l+r)>>1;
    int res=sum[L[y]]-sum[L[x]];
    if(k<=mid)
        query(L[x],L[y],l,mid,k);
    else
        return res+query(R[x],R[y],mid+1,r,k);
}

void init()
{
    tot=0;
}

int getnum(int num,int l,int r,int k)//查询小于等于k的数的个数
{
    int kk=lower_bound(b+1,b+num+1,k)-b;
    if(b[kk]!=k)
        kk--;
    if(kk==0)
        return 0;
    else
        return query(root[l-1],root[r],1,num,kk);
}

int main()
{
    int t,n,m,l,r,k,cas=1;
    scanf("%d",&t);
    while(t--)
    {
        init();
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]),b[i]=a[i];
        sort(b+1,b+n+1);
        int num=unique(b+1,b+1+n)-b-1;
        for(int i=1;i<=n;i++)
            a[i]=lower_bound(b+1,b+num+1,a[i])-b;

        root[0]=build(1,num);
        for(int i=1;i<=n;i++)
            root[i]=update(root[i-1],1,num,a[i]);
        printf("Case %d:\n",cas++);
        while(m--)
        {
            scanf("%d%d%d",&l,&r,&k);
            l++,r++;
            printf("%d\n",getnum(num,l,r,k));
        }
    }
    return 0;
}

hdu:6621

给定一个数组,有m次询问,每次询问l,r,p,k

每次询问区间(l,r)内,与p的差距第k小的数 与p的差的绝对值(包括p)

解析:

二分差值v

查询(p-v-1,p+v)中的数目,数目大于等于k的情况下,求最小的v

ac:

#include<bits/stdc++.h>
#define MAXN 3000005
using namespace std;
int a[MAXN],b[MAXN],root[MAXN];
int L[MAXN],R[MAXN];
int sum[MAXN];
int tot=0;

int build(int l,int r)
{
    int now=++tot;
    sum[now]=0;
    if(l!=r)
    {
        int mid=(l+r)>>1;
        L[now]=build(l,mid);
        R[now]=build(mid+1,r);
    }
    return now;
}

int update(int pre,int l,int r,int k)
{
    int now=++tot;
    sum[now]=sum[pre]+1;
    L[now]=L[pre],R[now]=R[pre];
    if(l!=r)
    {
        int mid=(l+r)>>1;
        if(k<=mid)
            L[now]=update(L[pre],l,mid,k);
        else
            R[now]=update(R[pre],mid+1,r,k);
    }
    return now;
}

int query(int x,int y,int l,int r,int k)
{
    if(sum[y]-sum[x]==0) return 0;
    if(l==r)
    return sum[y]-sum[x];
    int mid=(l+r)>>1;
    int res=sum[L[y]]-sum[L[x]];
    if(k<=mid)
        query(L[x],L[y],l,mid,k);
    else
        return res+query(R[x],R[y],mid+1,r,k);
}

void init()
{
    tot=0;
}

int getnum(int num,int l,int r,int k)//查询小于等于k的数的个数
{
    int kk=lower_bound(b+1,b+num+1,k)-b;
    if(b[kk]!=k)
        kk--;
    if(kk==0)
        return 0;
    else
        return query(root[l-1],root[r],1,num,kk);
}

int main()
{
    int t,n,m,l,r,k;
    scanf("%d",&t);
    while(t--)
    {
        init();
        scanf("%d%d",&n,&m);
        int maxs=0;
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]),b[i]=a[i],maxs=max(a[i],maxs);
        sort(b+1,b+n+1);
        int num=unique(b+1,b+1+n)-b-1;
        for(int i=1;i<=n;i++)
            a[i]=lower_bound(b+1,b+num+1,a[i])-b;
        root[0]=build(1,num);
        for(int i=1;i<=n;i++)
            root[i]=update(root[i-1],1,num,a[i]);
        int res=0,p;
        while(m--)
        {
            scanf("%d%d%d%d",&l,&r,&p,&k);
            l=l^res,r=r^res,p=p^res,k=k^res;
            int L=0,R=maxs;
            int ans=0;
            while(L<=R)
            {
                int mid=(L+R)>>1;
                int kk=getnum(num,l,r,p+mid)-getnum(num,l,r,p-mid-1);//查询(p-mid-1,p+mid)范围内的数的个数
                if(kk>=k)
                {
                    R=mid-1;
                    ans=mid;
                }
                else{
                    L=mid+1;
                }
            }
            printf("%d\n",ans);
            res=ans;
        }
    }
    return 0;
}

http://acm.hdu.edu.cn/showproblem.php?pid=6601

题意:给定一个木棍长度区间,让你在(l,r)区间里用三个木棍组成周长最大的三角形

解析:第k大+从大到小遍历选取最大的,要求边长a<b+c,如果没有-1,(r-l+1)<=2特判,注意可能需要swap(l,r)

https://vjudge.net/problem/HDU-6703

题意:

强制在线,有两种操作:

1.删除数a[x]

2.求在区间(1,r)中未出现,且>=k的最小的数

解析:

主席树(离线)+set

每次删除了数,直接加入到set中,删除的数,肯定不在(1,r)中,将在线转化为离线

主席树求区间(r+1,n+1)的第一个>=k的值,求set中第一个>=k的值

ac:

#include<bits/stdc++.h>
#define MAXN 3000005
using namespace std;
int L[MAXN],R[MAXN],sum[MAXN];
int root[MAXN],a[MAXN],b[MAXN];
int tot;

void init()
{
    tot=0;
}

int build(int l,int r)
{
    int now=++tot;
    sum[now]=0;
    if(l!=r)
    {
        int m=(l+r)>>1;
        L[now]=build(l,m);
        R[now]=build(m+1,r);
    }
    return now;
}

int update(int pre,int l,int r,int k)
{
    int now=++tot;
    sum[now]=sum[pre]+1;
    L[now]=L[pre],R[now]=R[pre];
    if(l!=r)
    {
        int m=(l+r)>>1;
        if(k<=m)
            L[now]=update(L[pre],l,m,k);
        else
            R[now]=update(R[pre],m+1,r,k);
    }
    return now;
}

int query(int pre,int now,int l,int r,int k)
{
    if(sum[now]-sum[pre]==0)//(l,r)范围内的数没有
        return 0;
    if(l==r)
        return l;
    int m=(l+r)>>1;
    if(k>m)         //k>m,只可能在右子树
        return query(R[pre],R[now],m+1,r,k);
    int ans=query(L[pre],L[now],l,m,k);
    if(ans)
        return ans;
    else
        return query(R[pre],R[now],m+1,r,k);//只能去右子树
}

set<int> st;
set<int>::iterator it;

int main()
{
    int t,n,m;
    scanf("%d",&t);
    while(t--)
    {
        st.clear();
        init();
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        a[n+1]=n+1;
        n=n+1;
        root[0]=build(1,n);
        for(int i=1;i<=n;i++)
            root[i]=update(root[i-1],1,n,a[i]);
        int op,res=0,x,r,k;
        while(m--)
        {
            scanf("%d",&op);
            if(op==1)
            {
                scanf("%d",&x);
                x=x^res;
                st.insert(a[x]);
            }
            else{
                scanf("%d%d",&r,&k);
                r=r^res,k=k^res;
                int ans=query(root[r+1-1],root[n],1,n,k);
                it=st.lower_bound(k);
                if(it!=st.end())
                    ans=min(ans,*it);
                printf("%d\n",ans);
                res=ans;
            }
        }
    }
    return 0;
}

https://ac.nowcoder.com/acm/contest/889/H

题意:

给定n棵竹子,高度位a[i]

m次询问

每次选择第L~第R棵竹子,分y次将所以竹子砍完,每次砍掉的竹子长度相同,没一次同时看这个区间的所以竹子,问第x次砍在什么位置

解析:

先前缀和a[i],第x次剩下的高度和=(sum[r]-sum[l-1])*(1-x/y).

查询最大高度多少时,高度和=(sum[r]-sum[l-1])*(1-x/y).sumh=sum(高度小于mid)+高度大于mid的数目*mid

建立主席树,二分最大高度,数目值和结合处理应用

ac:

#include<bits/stdc++.h>
#define ll long long
#define MAXN 200005
using namespace std;

int L[MAXN*24],R[MAXN*24];
ll sum[MAXN*24],num[MAXN*24],h[MAXN*2];
int root[MAXN*2];
int tot=0;

int build(int l,int r)
{
    int now=++tot;
    sum[now]=num[now]=0;
    if(l!=r)
    {
        int mid=(l+r)>>1;
        L[now]=build(l,mid);
        R[now]=build(mid+1,r);
    }
    return now;
}

int update(int pre,int l,int r,int k)
{
    int now=++tot;
    sum[now]=sum[pre]+k;num[now]=num[pre]+1;
    L[now]=L[pre];R[now]=R[pre];
    if(l!=r)
    {
        int mid=(l+r)>>1;
        if(k<=mid)
            L[now]=update(L[pre],l,mid,k);
        else
            R[now]=update(R[pre],mid+1,r,k);
    }
    return now;
}

double query(int now,int pre,int l,int r,double need,int kk)
{
    if(l==r)
    {
        return need/kk;//比l高的竹子的数目kk
    }
    int mid=(l+r)>>1;//二分砍的高度,sumax为砍后的竹子剩余高度和
    double sumax=sum[L[now]]-sum[L[pre]]+1LL*mid*(kk-(num[L[now]]-num[L[pre]]));//左子树的和(比mid矮的树的和)+比mid高的树全变为mid高度
    if(sumax>=need) //砍少了,继续砍
        return query(L[now],L[pre],l,mid,need,kk);
    else{           //砍多了,少砍点
        return query(R[now],R[pre],mid+1,r,need-(sum[L[now]]-sum[L[pre]]),kk-(num[L[now]]-num[L[pre]]));
    }
}

int main()
{
    int n,q,xx,l,r;
    double x,y;
    scanf("%d%d",&n,&q);
    root[0]=build(1,100000);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&xx);
        h[i]=h[i-1]+xx;
        root[i]=update(root[i-1],1,100000,xx);
    }
    while(q--)
    {
        scanf("%d%d%lf%lf",&l,&r,&x,&y);
        double need=(1.0-x/y)*(h[r]-h[l-1]);
        printf("%.10f\n",query(root[r],root[l-1],1,100000,need,r-l+1));
    }
    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值