1. 程式人生 > 實用技巧 >POJ2104 K-th Number (平方分割 + 二分)

POJ2104 K-th Number (平方分割 + 二分)


題目連結:傳送門



題意:輸入n個數,然後進行m次操作,每次操作輸入三個數l,r,k,輸出在[l,r]區間第k小的數

解題思路:這道題做法倒是挺多的,平方分割可以做,歸併樹,劃分樹,主席樹都能做,但是本片部落格主要講解一下平方分割的做法(比較簡單),我們把n個數分為n/1000個桶,然後我們維護每個桶即可,我們通過預處理把每個桶的元素進行排序處理,然後我們二分查詢第k小的數字,在[l,r]這個區間比第第k小的數字肯定只有k-1個,比第k大的數字只有(r-l+1)-k個,我們會發現我們查詢的區間無非就兩種情況,第一種,就是查詢區間完全被包含在區間內,第二種就是所在的桶不完全包含區間內的元素,需要我們逐步處理,時間複雜度為\(O(nlogn+m\sqrt{n}log^1.5n)\)

Code:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;

const int B = 1000, N = 100005;
int n,m;
int a[N],b[N];
vector<int> ton[N/B+1];

int main()
{
	int t;
	scanf("%d%d",&n,&m);
	for(int i = 0;i < n; ++i) {
		scanf("%d",&a[i]);
		ton[i/B].push_back(a[i]);
		b[i] = a[i];
	}
	sort(b,b+n);
	for(int i = 0,len = n/B;i < len; ++i) {
		sort(ton[i].begin(),ton[i].end());//對每一個桶進行預處理
	}
	int l,r,k;
	while(m--) {
		scanf("%d%d%d",&l,&r,&k);
		l--;
		int lb = -1, rb = n;
		while(lb + 1 < rb) {//二分查詢x
			int mid = (lb + rb) >> 1;
			int x = b[mid];
			int tl = l,tr = r, c = 0;
			while(tl < tr && tl % B != 0) if(a[tl++] <= x) c++;//左邊不在同一個桶裡面的
			while(tl < tr && tr % B != 0) if(a[--tr] <= x) c++;//右邊不在同一個桶裡面的
			while(tl < tr) {
				int bb = tl /B;
				c += upper_bound(ton[bb].begin(),ton[bb].end(),x) - ton[bb].begin();
				tl += B;
			}
			if(c >= k)
				rb = mid;
			else
				lb = mid;
		}
		printf("%d\n",b[rb]);
	}

	return 0;
}

PS:同樣的題目POJ能AC,在HDU會T,原因因該是HDU的資料和POJ的不太同,但是HDU的可以通過其他三種方法做出,學後再出關於歸併樹,劃分樹,主席樹的做法