幸福主席树(入门+题目推荐+模板思路)P3919/区间第K大/P3567/P2468/BZOJ5343

模板

这个模板是第二题相应代码只是作为模板不错。 题目基本都是基于一个模板写的所以懂得一个就懂了所有但是懒得改模板注释所以下面代码里的所有注释都没必要在意弄懂模板就可以了。
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;

const int maxn=1e6+100;

int a[maxn];
int rt[maxn];//rt[i]表示由数组前i个元素组成的线段树的根结点
struct node
{
    int l,r;//线段树左右子结点点
    int sum;//结点信息,表示这颗子树存在的元素的数目
}T[maxn*20];
int tot=0;//结点编号
vector<int> v;
int getid(int k)
{
    return lower_bound(v.begin(),v.end(),k)-v.begin()+1;
}
void build(int &o,int l,int r)//建立一颗空树
{
    o=++tot;
    T[o].sum=0;
    if(l==r) return;
    int mid=(l+r)/2;
    build(T[o].l,l,mid);
    build(T[o].r,mid+1,r);
}
void update(int l,int r,int &now,int last,int k)
{
    T[++tot]=T[last];//复制线段树
    //更新当前线段树的根结点
    now=tot;
    T[tot].sum++;
    if(l==r) return;//修改到叶子结点为止
    //根据需要修改的k来确定是修改左子树还是修改右子树
    int mid=(l+r)/2;
    if(k<=mid)
        update(l,mid,T[now].l,T[last].l,k);
    else
        update(mid+1,r,T[now].r,T[last].r,k);
}
int query(int l,int r,int x,int y,int k)//查询区间【x,y】中第小的数
{
    if(l==r) return l;//查询到叶子结点为止
    int mid=(l+r)/2;
    int cnt=T[T[y].l].sum-T[T[x].l].sum;//第y颗树比第x颗树在左子树上多的结点数
    if(cnt>=k)//答案在左子树上
        return query(l,mid,T[x].l,T[y].l,k);
    else
        return query(mid+1,r,T[x].r,T[y].r,k-cnt);
}
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            v.push_back(a[i]);
        }
        //build(,1,n);
        sort(v.begin(),v.end());
        v.erase(unique(v.begin(),v.end()),v.end());
        build(rt[0],1,n);
        for(int i=1;i<=n;i++)
            update(1,n,rt[i],rt[i-1],getid(a[i]));
        while(m--)
        {
            int x,y,k;
            scanf("%d%d%d",&x,&y,&k);
            printf("%d\n",v[query(1,n,rt[x-1],rt[y],k)-1]);
        }
    }
    return 0;
}


P3919 【模板】可持久化数组

题目链接:https://www.luogu.org/problem/P3919 https://blog.csdn.net/SSL_ZYC/article/details/88362417非常详细简洁的题解推荐。
#include<cstdio>
using namespace std;

const int maxn=1e6+10;//banben
const int maxm=maxn+20*maxn;//suoyoujiedian
#define lson l,mid
#define rson mid+1,r
int root[maxn],cou=1,a[maxn];

struct node{
    int xl,xr,val;
}tree[maxm*2];

int build(int l,int r)
{
    int rt=cou++;
    if(l==r)tree[rt].val=a[l];
    else{
        int mid=(l+r)>>1;
        tree[rt].xl=build(lson);
        tree[rt].xr=build(rson);
    }
    return rt;
}

int uppdate(int now,int l,int r,int index,int val){
    int rt=cou++;
    tree[rt]=tree[now];
    if(l==r)tree[rt].val=val;
    else{
        int mid=(l+r)>>1;
        if(index<=mid)tree[rt].xl=uppdate(tree[rt].xl,lson,index,val);
        else tree[rt].xr=uppdate(tree[rt].xr,rson,index,val);
    }
    return rt;
}

int query(int now,int l,int r,int index){
    int rt=cou++;
    tree[rt]=tree[now];
    if(l==r)printf("%d\n",tree[rt].val);
    else{
        int mid=(l+r)>>1;
        if(index<=mid)tree[rt].xl=query(tree[rt].xl,lson,index);
        else tree[rt].xr=query(tree[rt].xr,rson,index);
    }
    return rt;
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    root[0]=build(1,n);
    for(int i=1;i<=m;i++) {
        int rt, x;
        scanf("%d%d", &rt, &x);
        int index, val;
        if (x == 1) {
            scanf("%d%d", &index, &val);
            root[i]=uppdate(root[rt], 1, n, index, val);
        } else {
            scanf("%d", &index);
            root[i]=query(root[rt], 1, n, index);
        }
    }
}

区间第K大

其实这个。最好直接看题目然后看代码,看了博客看半天没怎么看懂代码看看就会。https://blog.csdn.net/woshinannan741/article/details/53012682

相应模板题:http://poj.org/problem?id=2104
模板出处:https://blog.csdn.net/qq_37685156/article/details/80350385
优秀模板

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;

const int maxn=1e6+100;

int a[maxn];
int rt[maxn];//rt[i]表示由数组前i个元素组成的线段树的根结点
struct node
{
    int l,r;//线段树左右子结点点
    int sum;//结点信息,表示这颗子树存在的元素的数目
}T[maxn*20];
int tot=0;//结点编号
vector<int> v;
int getid(int k)
{
    return lower_bound(v.begin(),v.end(),k)-v.begin()+1;
}
void build(int &o,int l,int r)//建立一颗空树
{
    o=++tot;
    T[o].sum=0;
    if(l==r) return;
    int mid=(l+r)/2;
    build(T[o].l,l,mid);
    build(T[o].r,mid+1,r);
}
void update(int l,int r,int &now,int last,int k)
{
    T[++tot]=T[last];//复制线段树
    //更新当前线段树的根结点
    now=tot;
    T[tot].sum++;
    if(l==r) return;//修改到叶子结点为止
    //根据需要修改的k来确定是修改左子树还是修改右子树
    int mid=(l+r)/2;
    if(k<=mid)
        update(l,mid,T[now].l,T[last].l,k);
    else
        update(mid+1,r,T[now].r,T[last].r,k);
}
int query(int l,int r,int x,int y,int k)//查询区间【x,y】中第小的数
{
    if(l==r) return l;//查询到叶子结点为止
    int mid=(l+r)/2;
    int cnt=T[T[y].l].sum-T[T[x].l].sum;//第y颗树比第x颗树在左子树上多的结点数
    if(cnt>=k)//答案在左子树上
        return query(l,mid,T[x].l,T[y].l,k);
    else
        return query(mid+1,r,T[x].r,T[y].r,k-cnt);
}
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            v.push_back(a[i]);
        }
        //build(,1,n);
        sort(v.begin(),v.end());
        v.erase(unique(v.begin(),v.end()),v.end());
        build(rt[0],1,n);
        for(int i=1;i<=n;i++)
            update(1,n,rt[i],rt[i-1],getid(a[i]));
        while(m--)
        {
            int x,y,k;
            scanf("%d%d%d",&x,&y,&k);
            printf("%d\n",v[query(1,n,rt[x-1],rt[y],k)-1]);
        }
    }
    return 0;
}

P3567 [POI2014]KUR-Couriers

题目链接:https://www.luogu.org/problem/P3567 这道题也是比较裸的题简单来讲求区间内有数字大于区间一般。 怎么写呢。只要判断么个节点左子树右子树那个大于区间一般,选择那个子树。然后返回叶子节点即可。 这提数据不用离散化。但是模板里有所以就写。一开始节点开了数组长度20倍但是不够。。。然后改了40倍超时了。最后加了02优化过了。。。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;

const int maxn=5e5+100;

int a[maxn];
int rt[maxn];//rt[i]表示由数组前i个元素组成的线段树的根结点
struct node
{
    int l,r;//线段树左右子结点点
    int sum;//结点信息,表示这颗子树存在的元素的数目
}T[maxn*40];
int tot=0;//结点编号
int mark=0;
vector<int> v;
int getid(int k)
{
    return lower_bound(v.begin(),v.end(),k)-v.begin()+1;
}
void build(int &o,int l,int r)//建立一颗空树
{
    o=++tot;
    T[o].sum=0;
    if(l==r) return;
    int mid=(l+r)/2;
    build(T[o].l,l,mid);
    build(T[o].r,mid+1,r);
}
void update(int l,int r,int &now,int last,int k)
{
    T[++tot]=T[last];//复制线段树
    //更新当前线段树的根结点
    now=tot;
    T[tot].sum++;
    if(l==r) return;//修改到叶子结点为止
    //根据需要修改的k来确定是修改左子树还是修改右子树
    int mid=(l+r)/2;
    if(k<=mid)
        update(l,mid,T[now].l,T[last].l,k);
    else
        update(mid+1,r,T[now].r,T[last].r,k);
}
int query(int l,int r,int x,int y,int k)//查询区间【x,y】中第小的数
{
    if(l==r){
        if(T[y].sum-T[x].sum>=k)return l;
        else return 0;
    }//查询到叶子结点为止
    int mid=(l+r)/2;
    int cnt=T[T[y].l].sum-T[T[x].l].sum;//第y颗树比第x颗树在左子树上多的结点数
    if(cnt>=k)//答案在左子树上
        return query(l,mid,T[x].l,T[y].l,k);
    else
        return query(mid+1,r,T[x].r,T[y].r,k);
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);

        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            v.push_back(a[i]);
        }
        //build(,1,n);
        sort(v.begin(),v.end());
        v.erase(unique(v.begin(),v.end()),v.end());
        build(rt[0],1,n);
        for(int i=1;i<=n;i++)
        update(1,n,rt[i],rt[i-1],getid(a[i]));
        while(m--)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            printf("%d\n",v[query(1,n,rt[x-1],rt[y],(y-x+1)/2+1)-1]);
        }

    return 0;
}

P2468 [SDOI2010]粟粟的书架

简单讲下题意:一个rc矩阵。每次给你一个矩阵块从里面可以选书。书有厚度,每天给你一个h值问当天选多少本书使厚度大h值。 看起来好难的样子。完全想不到主席树是我太菜了。。。后来看了题解发现数据分两部分。前一半部分是r行c列。后一半部分是一行c列。前半部分可以用二分写。后半部分用主席书写(听说前半部分用200课可能会爆。emmm没试过。)
前半部分。书厚度有单调性。求出最小厚度,试得比之大的书的综合大于h。二分即可没什么好说。
后半部分。就是把书输到线段树,记录每个节点数量和厚度。由于优先选择厚度较大的书所以先比较右子树,如果所需厚度大于右子树,那么记录右子树书数,寻找左子树。如果小于寻找右子树。(推一下样例很容易就看出来了没啥必要解释)。
参考题解:https://www.cnblogs.com/LLTYYC/p/10595822.html(我只写了后半部分,前半部分是参考这个但是后半部分跟他的不太一样)
#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int maxn=5e5+100;

int a[maxn];
int rt[maxn];//rt[i]表示由数组前i个元素组成的线段树的根结点
struct node
{
    int l,r;//线段树左右子结点点
    int sz,sum;
}T[maxn*40];
int tot=0;//结点编号
vector<int> v;
const int N=207,M=5e5+7,INF=1e9+7;
int Q;
int n,m;
int cnt[N][N][1007],sum[N][N][1007];//sum是总和,cnt是数量
int xa,ya,xb,yb,H,mx;
inline int clac(int p) { return sum[xb][yb][p]-sum[xa-1][yb][p]-sum[xb][ya-1][p]+sum[xa-1][ya-1][p]; }//求一个矩形的sum
inline int CNT(int p) { return cnt[xb][yb][p]-cnt[xa-1][yb][p]-cnt[xb][ya-1][p]+cnt[xa-1][ya-1][p]; }//求一个矩形的cnt
inline int query()//二分答案
{
    int res=-1,l=0,r=mx+1,mid=l+r>>1;
    while(l<=r)
    {
        mid=l+r>>1;
        if(clac(mid)>=H) l=mid+1,res=mid;
        else r=mid-1;
    }
    return res;
}
int bk[N][N];
void solve1()
{
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++) bk[i][j]=read(),mx=max(mx,bk[i][j]);
    for(int k=0;k<=mx;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            {
                sum[i][j][k]=sum[i-1][j][k]+sum[i][j-1][k]-sum[i-1][j-1][k]+(bk[i][j]>=k ? bk[i][j] : 0);
                cnt[i][j][k]=cnt[i-1][j][k]+cnt[i][j-1][k]-cnt[i-1][j-1][k]+(bk[i][j]>=k ? 1 : 0);
            }
    while(Q--)
    {
        xa=read(),ya=read(),xb=read(),yb=read(),H=read();
        int t=query();
        if(t==-1) printf("Poor QLW\n");
        else printf("%d\n", CNT(t) - (clac(t)-H)/t );//注意特判厚度为t的书不全取
    }
}

void build(int &o,int l,int r)//建立一颗空树
{
    o=++tot;
    T[o].sz=0;
    T[o].sum=0;
    if(l==r) return;
    int mid=(l+r)/2;
    build(T[o].l,l,mid);
    build(T[o].r,mid+1,r);
}
void update(int l,int r,int &now,int last,int k)
{
    T[++tot]=T[last];//复制线段树
    //更新当前线段树的根结点
    now=tot;
    T[tot].sz++;
    T[tot].sum+=k;
    if(l==r) return;//修改到叶子结点为止
    //根据需要修改的k来确定是修改左子树还是修改右子树
    int mid=(l+r)/2;
    if(k<=mid)
        update(l,mid,T[now].l,T[last].l,k);
    else
        update(mid+1,r,T[now].r,T[last].r,k);
}
int query(int l,int r,int x,int y,int k)//查询区间【x,y】中第小的数
{
    if(l==r){
        return (k+l-1)/l;
    }//查询到叶子结点为止
    int mid=(l+r)/2;
    int cnt=T[T[y].r].sum-T[T[x].r].sum;
    int cou=0;
    if(cnt<k){
        cou+=query(l,mid,T[x].l,T[y].l,k-cnt);
    cou+=T[T[y].r].sz-T[T[x].r].sz;}
    else
        cou+=query(mid+1,r,T[x].r,T[y].r,k);
//    cout<<l<<r<<cou<<endl;
    return cou;
}
int solve2(){
    build(rt[0],1,1000);
    for(int i=1;i<=m;i++)
    {
        a[i]=read();
        update(1,1000,rt[i],rt[i-1],a[i]);
    }
    while(Q--)
    {
        int x1,y1,x2,y2,h;
        x1=read(),y1=read(),x2=read(),y2=read(),h=read();
        if(T[rt[y2]].sum-T[rt[y1-1]].sum<h){printf("Poor QLW\n");continue;}
        else printf("%d\n",query(1,1000,rt[y1-1],rt[y2],h));
    }
}
int main()
{
    n=read(),m=read(),Q=read();
    if(n>1) solve1();
    else solve2();
    return 0;
}

BZOJ5343: Ctsc2018混合果汁

题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=5343
优秀题解:https://blog.csdn.net/weixin_34377065/article/details/92658339
只看了思路感觉二分很难想啊好烦。题解不错。暂时先不写。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值