主席树入门
Posted yanlifneg
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了主席树入门相关的知识,希望对你有一定的参考价值。
/* 主席树入门 从一个题目切入吧 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; }
以上是关于主席树入门的主要内容,如果未能解决你的问题,请参考以下文章