zoukankan      html  css  js  c++  java
  • 主席树入门

    /*
    主席树入门
    
    从一个题目切入吧
    HDU6230 Palindrome 
    最后转化成求区间[l,r]里面有几个数比x小 
    一开始就想无脑主席树
    之前只会了一个板子 很不灵活 只会查第k小
    然后二分i是第几小,套上主席树 这时候会多一个二分logn
    然后跑的比较慢在超时的边缘试探 
    然后看到了划分树这个东西 还蛮好理解就写了一发
    依旧在超时的边缘
    其实查有几个比x小的数不需要套那一层二分
    只要理解好主席树 就可以扔掉二分 
    下面简单说一下我理解的这种比较神奇的数据结构
    
    也是根据题目来说 板子题 poj2104
    我们开一颗线段树 叶子节点就表示i出现的次数
    准确的说 是前缀出现的次数 那就有n棵线段树
    查询的时候利用前缀和的思想来做
    下面优化空间 
    我们考虑从i到i+1发生了什么变化
    只加进来一个数 之后从根节点到他的这条链发生了改变
    因此我们只记录这个东西
    这样就很好的解决了上面的问题
    注意存的是每个数的个数 下标是数的大小 如果数字比较大要离散化
    
    
    然后这个是poj2104的代码 
    */
    #include<cstdio>
    #include<algorithm>
    #define maxn 60010
    #define mid (l+r)/2
    using namespace std;
    int n,m,num,a[maxn],A[maxn],s[maxn],lc[maxn],rc[maxn],r[maxn],cnt;
    int init(){
        int x=0,f=1;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
        return x*f;
    }
    int Build(int S,int L,int R){
        cnt++;s[cnt]=S;
        lc[cnt]=L;rc[cnt]=R;
        return cnt;
    }
    void Insert(int &now,int pre,int l,int r,int k){
        now=Build(s[pre]+1,lc[pre],rc[pre]);
        if(l==r)return;
        if(k<=mid)Insert(lc[now],lc[pre],l,mid,k);
        else Insert(rc[now],rc[pre],mid+1,r,k);
    }
    int Query(int L,int R,int l,int r,int k){
        if(l==r)return l;
        int sum=s[lc[R]]-s[lc[L]];
        if(sum>=k)return Query(lc[L],lc[R],l,mid,k);
        else return Query(rc[L],rc[R],mid+1,r,k-sum);
    }
    int main()
    {
        n=init();m=init();
        for(int i=1;i<=n;i++){
            a[i]=init();A[i]=a[i];
        }
        sort(A+1,A+1+n);
        num=unique(A+1,A+1+n)-A-1;
        for(int i=1;i<=n;i++){
            int pos=lower_bound(A+1,A+1+num,a[i])-A;
            Insert(r[i],r[i-1],1,num,pos);
        }
        for(int i=1;i<=m;i++){
            int L=init(),R=init();
            int pos=(R-L)/2+1;
            pos=Query(r[L-1],r[R],1,num,pos);
            printf("%d
    ",A[pos]);
        }
        return 0;
    }
    /*
     然后我们回到hdu6230 考虑快速查询区间比x小的数的个数
     先看下施展套一个二分的无脑主席树 2800ms+ (考虑到数字和下标一样 就没有离散化) 
    */
    
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #define mid (l+r)/2
    #define maxn 500010
    #define ll long long
    using namespace std;
    int T,n,len[maxn],a[maxn],s[maxn*30],lc[maxn*30],rc[maxn*30],r[maxn*30],cnt; 
    ll ans;
    char c[maxn];
    void Clear(){
        memset(len,0,sizeof(len));
        memset(s,0,sizeof(s));
        memset(c,0,sizeof(c));
        ans=0;cnt=0; 
    }
    void Ready(){
        c[0]='#';c[n+1]='#';
    }
    void Mar(){
        int mx=-1,id=-1;
        for(int i=1;i<=n;i++){
            if(i<=id+mx)len[i]=min(len[2*id-i],id+mx-i);
            while(i-len[i]-1>=1&&i+len[i]+1<=n&&c[i-len[i]-1]==c[i+len[i]+1])len[i]++;
            if(i+len[i]>id+mx)id=i,mx=len[i];
        }
    }
    int Build(int S,int L,int R){
        cnt++;s[cnt]=S;
        lc[cnt]=L;rc[cnt]=R;
        return cnt;
    }
    void Insert(int &now,int pre,int l,int r,int k){
        now=Build(s[pre]+1,lc[pre],rc[pre]); 
        if(l==r)return;
        if(k<=mid)Insert(lc[now],lc[pre],l,mid,k);
        else Insert(rc[now],rc[pre],mid+1,r,k);
    }
    int Query(int L,int R,int l,int r,int k){
        if(l==r)return l;
        int sum=s[lc[R]]-s[lc[L]];
        if(sum>=k)return Query(lc[L],lc[R],l,mid,k);
        else return Query(rc[L],rc[R],mid+1,r,k-sum);
    }
    void Solve(){
        for(int i=1;i<=n;i++)
            a[i]=i-len[i];
        for(int i=1;i<=n;i++){
            Insert(r[i],r[i-1],1,n,a[i]);
        }
        for(int i=1;i<=n;i++){
            int L=i+1,R=i+len[i];
            int Li=1,Ri=R-L+1;
            while(Li<=Ri){
                int Mid=(Li+Ri)/2;
                int pos=Query(r[L-1],r[R],1,n,Mid);
                if(pos<=i)Li=Mid+1;
                else Ri=Mid-1;
            }
            ans+=Li-1;
        }
    }
    int main(){
        scanf("%d",&T);
        while(T--){
            Clear();scanf("%s",c+1);n=strlen(c+1);
            Ready();Mar();Solve();printf("%lld
    ",ans);
        }
        return 0;
    }
    
    /*这个是套的划分树  时间差不多*/
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define mid (l+r)/2
    #define maxn 500010
    #define ll long long
    using namespace std;
    int T,n,len[maxn],a[maxn],val[20][maxn],num[20][maxn];
    ll ans;
    char ss[maxn],c[maxn];
    void Clear(){
        memset(len,0,sizeof(len));
        memset(a,0,sizeof(a));ans=0;
        memset(num[0],0,sizeof(num[0]));
    }
    void Ready(){
        c[0]='#';c[n+1]='#';
    }
    void Mar(){
        int mx=-1,id=-1;
        for(int i=1;i<=n;i++){
            if(i<=id+mx)len[i]=min(len[2*id-i],id+mx-i);
            while(i-len[i]-1>=1&&i+len[i]+1<=n&&c[i-len[i]-1]==c[i+len[i]+1])len[i]++;
            if(i+len[i]>id+mx)id=i,mx=len[i];
        }
    }
    void Build(int l,int r,int c){
        if(l==r)return;int isame=mid-l+1;//isame保存有多少和sorted[mid]一样大的数进入左孩子
        for(int i=l;i<=r;i++)if(val[c][i]<a[mid])isame--;
        int ln=l,rn=mid+1;//本结点两个孩子结点的开头,ln左
        for(int i=l;i<=r;i++){
            if(i==l)num[c][i]=0;
            else num[c][i]=num[c][i-1];
            if(val[c][i]<a[mid]||(val[c][i]==a[mid]&&isame>0)){
                val[c+1][ln++]=val[c][i];num[c][i]++;
                if(val[c][i]==a[mid])isame--;
            }
            else val[c+1][rn++]=val[c][i];
        }
        Build(l,mid,c+1);Build(mid+1,r,c+1);
    }
    int Query(int c,int sl,int sr,int l,int r,int k){
        if(sl==sr)return val[c][sl];
        int ly;if(l==sl)ly=0;else ly=num[c][l-1];//ly 表示l 前面有多少元素进入左孩子
        int tolef=num[c][r]-ly;  //这一层l到r之间进入左子树的有tolef个
           if(tolef>=k){
            return Query(c+1,sl,(sl+sr)/2,sl+ly,sl+num[c][r]-1,k);
           }
           else{
             // l-sl 表示l前面有多少数,再减ly 表示这些数中去右子树的有多少个
             int lr = (sl+sr)/2 + 1 + (l-sl-ly);  //l-r 去右边的开头位置
             // r-l+1 表示l到r有多少数,减去去左边的,剩下是去右边的,去右边1个,下标就是lr,所以减1
             return Query(c+1,(sl+sr)/2+1,sr,lr,lr+r-l+1-tolef-1,k-tolef);
           }
    }
    void Solve(){
        
        for(int i=1;i<=n;i++){
            a[i]=i-len[i];val[0][i]=a[i];
        }
        sort(a+1,a+1+n);Build(1,n,0);
        for(int i=1;i<=n;i++){
            int L=i+1,R=i+len[i];
            int Li=1,Ri=R-L+1;
            while(Li<=Ri){
                int Mid=(Li+Ri)/2;
                if(Query(0,1,n,L,R,Mid)<=i)Li=Mid+1;
                else Ri=Mid-1;
            }
            ans+=Li-1;
        }
    }
    int main(){
        scanf("%d",&T);
        while(T--){
            Clear();scanf("%s",c+1);n=strlen(c+1);
            Ready();Mar();Solve();printf("%lld
    ",ans);
        }
        return 0;
    }
    /*
    
    很慢啊这样子施展 随便加几组数据就要GG了 
    因为是按下标存的 找<=x的数的个数 实际上就是1-x的区间和
    我们退化回线段树的思想  就简单的区间查询就好了 去掉了二分
    1200+ms 
    
    */
    
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define mid (l+r)/2
    #define maxn 500010
    #define ll long long
    using namespace std;
    int T,n,len[maxn],a[maxn],s[maxn*30],lc[maxn*30],rc[maxn*30],r[maxn*30],cnt;
    ll ans;
    char c[maxn];
    void Clear(){
        memset(len,0,sizeof(len));
        memset(c,0,sizeof(c));
        ans=0;cnt=0;
    }
    void Ready(){
        c[0]='#';c[n+1]='#';
    }
    void Mar(){
        int mx=-1,id=-1;
        for(int i=1;i<=n;i++){
            if(i<=id+mx)len[i]=min(len[2*id-i],id+mx-i);
            while(i-len[i]-1>=1&&i+len[i]+1<=n&&c[i-len[i]-1]==c[i+len[i]+1])len[i]++;
            if(i+len[i]>id+mx)id=i,mx=len[i];
        }
    }
    int Build(int S,int L,int R){
        cnt++;s[cnt]=S;
        lc[cnt]=L;rc[cnt]=R;
        return cnt;
    }
    void Insert(int &now,int pre,int l,int r,int k){
        now=Build(s[pre]+1,lc[pre],rc[pre]);
        if(l==r)return;
        if(k<=mid)Insert(lc[now],lc[pre],l,mid,k);
        else Insert(rc[now],rc[pre],mid+1,r,k);
    }
    int Query(int L,int R,int x,int y,int l,int r){
        if(x<=l&&y>=r)return s[R]-s[L];
        int res=0;
        if(x<=mid)res+=Query(lc[L],lc[R],x,y,l,mid);
        if(y>mid)res+=Query(rc[L],rc[R],x,y,mid+1,r);
        return res;
    }
    void Solve(){
        for(int i=1;i<=n;i++)
            a[i]=i-len[i];
        for(int i=1;i<=n;i++)
            Insert(r[i],r[i-1],1,n,a[i]);
        for(int i=1;i<=n;i++){
            int L=i+1,R=i+len[i];
            if(L>R)continue;
            ans+=Query(r[L-1],r[R],1,i,1,n);
        }
    }
    int main(){
        scanf("%d",&T);
        while(T--){
            Clear();scanf("%s",c+1);n=strlen(c+1);
            Ready();Mar();Solve();printf("%lld
    ",ans);
        }
        return 0;
    }
  • 相关阅读:
    简单图片预加载
    前端进行图片压缩
    原生js实现拖动滑块验证
    chrome和IE下的滚动条样式修改
    简单canvas刮刮乐
    时间轴
    简单边框动画
    滚动指示器
    美化checkbox多选框
    将过长的文字改用省略号显示
  • 原文地址:https://www.cnblogs.com/yanlifneg/p/9544175.html
Copyright © 2011-2022 走看看