zoukankan      html  css  js  c++  java
  • 可持久化线段树(主席树)【舰娘系列】【自编题】

    这里写图片描述
    [pixiv] https://www.pixiv.net/member_illust.php?mode=medium&illust_id=60083619
    向大(hei)佬(e)势力学(di)习(tou)

    前段时间做了一套大佬自己出的题(大佬竟然是个宅男2333),蒟蒻的我自然是只得了30分的暴力分:-(

    fleet

    舰队

    【题目描述】
    舰队里的每位舰娘都有一个编号i,也有一个类型ti,例如驱逐舰、轻巡洋
    舰、航空母舰……
    每次提督都想知道从第l 位舰娘到第r 位舰娘中(包含l、r),一共有多
    少不同的类型。请你回答他的询问。
    【输入格式】
    第一行一个整数n,表示舰娘数量。
    第二行n 个整数,第i 个整数ti 表示舰娘i 的类型。
    第三行一个整数q,表示提督的询问数量。
    接下来q 行,每行2 个整数l,r,表示询问[l,r]有多少不同类型的舰娘。
    为了模拟真实情况,本题强制在线。你需要记录上一次的答案lastans(初
    始值为0),每次询问真实的l,r 为l xor lastans 与r xor lastans,其中xor
    表示按位异或操作。
    【输出格式】
    输出q 行,每行一个整数表示询问的答案。
    【样例数据】
    fleet.in
    5
    1 1 2 1 3
    3
    1 5
    1 7
    1 7
    fleet.out
    3
    2
    3
    【数据范围】
    对于30%的数据,n,q≤5000。
    对于另30%的数据,ti≤100000。
    对于100%的数据,1≤n,q≤200000,1≤l≤r≤n,1≤ti≤10^9。

    正解当然不是我想出来的,而是另一个大佬自己yy出来的

    我们建的线段树表示原序列中出现的不同的数的个数,就是对原序列建树,只不过对于节点i的线段树表示1~i区间出现的不同数的个数。
    建树的时候需要记录这一个数上一次出现的位置,新建一棵树的时候既要加,又要减。
    查询的时候利用前缀和区间查询即可
    注意要离散化
    然后就没更多的技巧了,但是将这一模型抽出来着实很厉害

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    const int N=200000+5;
    
    struct Node {
        Node *ls,*rs;
        int sum;
        void update(){
            sum=ls->sum+rs->sum;
        }
    }*root[N],*null,pool[N*40],*tail=pool;
    struct tt{
        int ty,nu;
    }t[N];
    int n,pre[N];
    
    bool cmp1(tt a,tt b){
        return a.ty<b.ty;
    }
    bool cmp2(tt a,tt b){
        return a.nu<b.nu;
    }
    Node *newnode(){
        Node *nd=++tail;
        nd->ls=nd->rs=null;
        nd->sum=0;
        return nd;
    }
    Node *build(int le,int ri){
        Node *nd=newnode();
        if(le==ri) return nd;
        int mid=(le+ri)>>1;
        nd->ls=build(le,mid);
        nd->rs=build(mid+1,ri);
    }
    void insert(Node *&ndn,Node *ndp,int le,int ri,int pos,int val){
        ndn=newnode();
        ndn->sum=ndp->sum+val;
        ndn->ls=ndp->ls,ndn->rs=ndp->rs;
        if(le==ri) return ;
        int mid=(le+ri)>>1;
        if(pos<=mid) insert(ndn->ls,ndp->ls,le,mid,pos,val);
        else insert(ndn->rs,ndp->rs,mid+1,ri,pos,val);
    }
    int query(Node *nd,int le,int ri,int L,int R){
        if(L<=le&&ri<=R){//printf("eh ");
            return nd->sum;
        }
        int mid=(le+ri)>>1;
        int rt=0;
        if(L<=mid) rt+=query(nd->ls,le,mid,L,R);
        if(mid<R) rt+=query(nd->rs,mid+1,ri,L,R);
        return rt;
    }
    int main(){
        freopen("fleet.in","r",stdin);
        freopen("fleet.out","w",stdout);
        null=++tail;
        null->ls=null->rs=null;
        null->sum=0;
    
        scanf("%d",&n);
        root[0]=build(1,n);
        for(int i=1;i<=n;i++){
            scanf("%d",&t[i].ty);
            t[i].nu=i;
        }
        sort(t+1,t+n+1,cmp1);
        int sz=0,tmp=0;
        for(int i=1;i<=n;i++){
            if(tmp!=t[i].ty){
                tmp=t[i].ty;sz++;t[i].ty=sz;
            }
            else t[i].ty=sz;
        }
        sort(t+1,t+n+1,cmp2);
        tmp=0;
        for(int i=1;i<=n;i++){
            insert(root[i],root[i-1],1,n,i,1);
            if(pre[t[i].ty]){//printf("he ");
                tmp=pre[t[i].ty];
                insert(root[i],root[i],1,n,tmp,-1);
            } 
            pre[t[i].ty]=i;
        }
        int q,l,r,ans=0;
        scanf("%d",&q);
        while(q--){
            scanf("%d%d",&l,&r);
            l^=ans;r^=ans;
            ans=query(root[r],1,n,l,r);
            printf("%d
    ",ans);
        }
        return 0;
    }

    总结:
    1、遇到区间的询问的题说不定就是主席树
    2、如何灵活地运用线段树也是一大技巧,线段树可没有这么简单,高深着呢!

  • 相关阅读:
    rest framework 认证 权限 频率
    rest framework 视图,路由
    rest framework 序列化
    10.3 Vue 路由系统
    10.4 Vue 父子传值
    10.2 Vue 环境安装
    10.1 ES6 的新增特性以及简单语法
    Django 跨域请求处理
    20190827 On Java8 第十四章 流式编程
    20190825 On Java8 第十三章 函数式编程
  • 原文地址:https://www.cnblogs.com/LinnBlanc/p/7763153.html
Copyright © 2011-2022 走看看