hdu 4417,poj 2104 划分树(模版)归并树(模版)
这次是彻底把划分树搞明确了,与此同一时候发现了模版的重要性。敲代码一个字符都不能错啊~~~
划分树具体解释:点击打开链接
题意:求一组数列中随意区间不大于h的个数。
这个题的做法是用二分查询 求给定区间内的中值再与K进行比較。
重点介绍划分树:
数据结构:
t[20][maxn] // 树结构,划分树存储
sum[20][maxn] // 记录该行[l,i] 中i到l有多少个存在左子树中
as[maxn] //原始数组排序后结果
#include <stdio.h> #include <string.h> #include <math.h> #include <bitset> #include <iostream> #include <algorithm> #define inf 0x3fffffff #define mid ((l+r)>>1) const int maxn=100000+100; typedef unsigned __int64 ull; using namespace std; int N,M; int t[30][maxn]; // 树结构,划分树存储 int sum[30][maxn]; // 记录该行[l,i] 中i到l有多少个存在左子树中 int as[maxn];//排序后数组 //建树是根据上一层建立下一层,所以t[p+1][ls++]=t[p][i]; l==r推断的条件放在循环后是为了让每一个元素都到最底层 void build(int p,int l,int r) { int ls=l,rs=mid+1; int lm=0,i; for(i=rs-1;i>=l;i--)// ls rs 左右子树開始的位置 lm 放入左子树的中值数目(避免中值过多树不平衡) if(as[i]==as[mid]) lm++; else break; for(i=l;i<=r;i++) { if(i==l) sum[p][i]=0;//sum的计算若为初始则为0。注意这里的每行sum有多个分开的树 else sum[p][i]=sum[p][i-1]; if(t[p][i]==as[mid]) if(lm) //将部分中值的数放入左边 lm--,sum[p][i]++,t[p+1][ls++]=t[p][i]; else t[p+1][rs++]=t[p][i]; else if(t[p][i]<as[mid]) sum[p][i]++,t[p+1][ls++]=t[p][i];//小于中值放入左边 else t[p+1][rs++]=t[p][i];//大于放入右边。sum与其无关 } if(l==r) return; build(p+1,l,mid); build(p+1,mid+1,r); } /* 在p层,[l,r]范围内查询[ql,qr]中第K大数 l+s 跳过查询区间前放入左子树个数,l+sum[p][qr]-1 [l,qr]放入左子树的个数 ql-l-s 查询区间前放入右子树的个数,qr-l-sum[p][qr] [l,qr]放入右子树的个数 */ int query(int p,int l,int r,int ql,int qr,int k) { if(l==r) return t[p][l]; int s,ss;//s是ql左边有多少放入下层左子树,ss是[ql,qr]中有多少放入下层左子树 if(ql==l) s=0,ss=sum[p][qr]; else s=sum[p][ql-1],ss=sum[p][qr]-s; if(k<=ss) return query(p+1,l,mid,l+s,l+sum[p][qr]-1,k); else return query(p+1,mid+1,r,mid+1+ ql-l-s,mid+1 +qr-l-sum[p][qr],k-ss); } int main() { int T; int n,m,cas=1; int i,l,r,k; scanf("%d",&T); while(T--) { scanf("%d%d",&N,&M); for(i=0;i<N;i++) scanf("%d",as+i),t[0][i]=as[i]; sort(as,as+N); build(0,0,N-1); printf("Case %d:\n",cas++); for(i=0;i<M;i++) { scanf("%d%d%d",&l,&r,&k); int mi=1,ma=r-l+1,Mid,ans=r-l+2; while(mi<=ma) { Mid=(mi+ma)>>1; int tmp=query(0,0,N-1,l,r,Mid); if(tmp>k) { ans=Mid; ma=Mid-1; } else mi=Mid+1; } printf("%d\n",ans-1); } } return 0; }
归并树:
就是在归并过程中保存结果。由于归并排序与线段树建树类似都是自底向上,所以能够保存。(这个比划分树好理解多了)
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<map> #include<stack> #include<algorithm> #include<string> #define LL long long #define LD long double #define eps 1e-7 #define inf 1<<30 #define MOD 1000000007 #define N 100005 using namespace std; struct MergeTree{ int left,right,mid; }tree[N*4]; int num[N],mer[20][N]; int n,q; void create(int step,int l,int r,int deep){ tree[step].left=l; tree[step].right=r; tree[step].mid=(l+r)>>1; if(l==r){ mer[deep][l]=num[l]; return; } create(step<<1,l,(l+r)/2,deep+1); create((step<<1)|1,(l+r)/2+1,r,deep+1); int i=l,j=(l+r)/2+1,p=l; //归并排序。在建树的时候保存 while(i<=(l+r)/2&&j<=r){ if(mer[deep+1][i]>mer[deep+1][j]) mer[deep][p++]=mer[deep+1][j++]; else mer[deep][p++]=mer[deep+1][i++]; } while(i<=(l+r)/2) mer[deep][p++]=mer[deep+1][i++]; while(j<=r) mer[deep][p++]=mer[deep+1][j++]; } int query(int step,int l,int r,int deep,int key){ if(tree[step].right<l||tree[step].left>r) return 0; if(tree[step].left>=l&&tree[step].right<=r) //找到key在排序后的数组中的位置 return lower_bound(&mer[deep][tree[step].left],&mer[deep][tree[step].right]+1,key)-&mer[deep][tree[step].left]; return query(2*step,l,r,deep+1,key)+query(2*step+1,l,r,deep+1,key); } int slove(int l,int r,int k){ int high=n,low=1,mid; //二分答案 while(low<high){ mid=(low+high+1)>>1; int cnt=query(1,l,r,1,mer[1][mid]); if(cnt<=k) low=mid; else high=mid-1; } return mer[1][low]; } int main(){ while(scanf("%d%d",&n,&q)!=EOF){ for(int i=1;i<=n;i++) scanf("%d",&num[i]); create(1,1,n,1); while(q--){ int l,r,k; scanf("%d%d%d",&l,&r,&k); printf("%d\n",slove(l,r,k-1)); } } return 0; }