求區間第k大之——歸併樹(poj2104)

歸併樹與線段樹的區別是,線段樹的結點維護一個值,而歸併樹的結點維護一個有序數組。每個節點的數列是其兩個兒子節點的數列合併後的結果。這顆線段樹正是歸併排序的完整再現。

要計算在某個區間上不大於x的數的個數,只需:

  • 如果所給區間與當前區間沒有交集,返回0
  • 如果所給區間完全包含當前區間,使用二分搜索找出當前區間的不大於x的數的下標再加1,返回這個值
  • 否則對兩個兒子遞歸地進行計算之後返回他們的和。

由於每個深度只訪問常數個結點,因此找出某區間內不超過x的數的個數的時間複雜度是O(log2n)

歸併樹找區間內第k大時間複雜度:
建樹:O(nlogn)
二分法找x:O(logn)
求不超過x的數的個數:O(log2 n)
整個算法的時間複雜度:O(nlogn+mlog3n).m爲詢問個數

public class Main{
	static int maxn=100100,n,arr[]=new int[8*maxn];//注意如果擴n的話開4*maxn會不夠用
	static ArrayList<Integer> dat[]=new ArrayList[8*maxn];
	static int upper_bound(ArrayList<Integer> a,int x) {
		int l=-1;
		int r=a.size();
		while(r-l>1) {
			int mid=(r+l)/2;
			if(a.get(mid)<=x) {
				l=mid;
			}else {
				r=mid;
			}
		}
		return l;
	}
	static void merge(ArrayList<Integer> arr1,ArrayList<Integer> arr2,ArrayList<Integer> target){//將有序數列arr1、arr2合併爲有序的target.
		int i=0;
		int j=0;
		while(i<arr1.size()&&j<arr2.size()) {
			if(arr1.get(i)<arr2.get(j)) {
				target.add(arr1.get(i++)); 
			}else {
				target.add(arr2.get(j++));
			}
		}
		while(i<arr1.size()) {
			target.add(arr1.get(i++));
		}
		while(j<arr2.size()) {
 			target.add(arr2.get(j++));
		}
	}
	static void init(int _n) {//將n擴大到2的冪。
		n=1;
		while(n<_n) {
			n*=2;
		}
	static void init(int rt,int l,int r) {//建樹。
		dat[rt]=new ArrayList<Integer>();//每次用到的時候再初始化,如果一開始把8*maxn個全初始化會超時!
		if(r-1==l) {	
			dat[rt].add(arr[l]);
		}else {
			int mid=(l+r)/2;
			init(rt*2+1,l,mid);
			init(rt*2+2,mid,r);
			merge(dat[rt*2+1],dat[rt*2+2],dat[rt]);
		}
	}
	static int query(int i,int j,int x,int l,int r,int rt) {//查詢原數列區間[i,j)內不大於x的數的個數
		
		if(r<=i||l>=j) {
			return 0;
		}else if(l>=i&&r<=j) {//完全包含
			return upper_bound(dat[rt],x)+1;
		}else {
			int lc=query(i,j,x,l,(l+r)/2,2*rt+1);
			int rc=query(i,j,x,(l+r)/2,r,2*rt+2);
			return lc+rc;
		}
	}
	public static void main(String args[]) throws IOException {
			PrintWriter out=new PrintWriter(System.out);
			InputReader sc=new InputReader(System.in);
			int nums[]=new int[maxn];
			n=sc.nextInt();
			int N=n;
			int m=sc.nextInt();
			for(int i=0;i<n;i++) {
				arr[i]=sc.nextInt();
				nums[i]=arr[i];
			}
			Arrays.sort(nums,0,n);
			init(n);
			init(0,0,n);

			for(int i=0;i<m;i++) {
				int I=sc.nextInt()-1;
				int J=sc.nextInt();
				int X=sc.nextInt();
				int l=-1;
				int r=N;			
				while(r-l>1) {//在原數列的排序數列nums[]中尋找到滿足在[i,j)範圍內有不少於k個小於等於x的x值的lower_bound下標r。
					int mid=(l+r)/2;
					int c=0;
					c=query(I,J,nums[mid],0,n,0);
					if(c>=X) {
						r=mid;
					}else {
						l=mid;
					}
				}
				//out.println(r);
				out.println(nums[r]);
			}
			
			out.flush();
			out.close();
		}
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章