poj2104 (線段樹求區間第k大)
阿新 • • 發佈:2019-02-15
題目連結:
題意:
給n個數, 每次詢問一個區間, 讓你輸出區間中的第k大的數。
做了這道題,我對線段樹的能解決的問題得認識又有提升。 這個是讓求區間內的第k大, 詢問比較多,暴力顯然不行。 我們想用線段樹解題,那麼線段樹應該儲存什麼呢? 一開始更本沒想到, 看了別人的部落格明白了, 原來是把線段樹上的每一個區間都變成有序的, 並把這個排列的順序存在另一個數組裡, 我們只在線段樹中存下這一段序列的起點和終點;之後我們就可以二分查找了。 最後的結果也是我們二分得出, 怎麼得出的呢? 方法是: 線段樹中每個區間都是有序的, 我們就可以用二分列舉數去查詢在這個區間內比當前列舉的數小或相等的個數是多少, 我們只需要找到滿足個數為K的那個最小的數;這個數就是我們要找到答案。
#include<stdio.h> #include<string.h> #include<algorithm> using namespace std; #define lson l, m, rt<<1 #define rson m+1, r, rt<<1|1 const int maxn = 100010; const int inf = 1e9; struct Node { int l, r; }tree[maxn<<2]; int d[maxn*100]; int top, a; void build(int l, int r, int rt) { if(l == r) { scanf("%d", &a); d[++top] = a; tree[rt].l = top; tree[rt].r = top; return ; } int m = (l+r)>>1; build(lson); build(rson); int ll = tree[rt<<1].l, lr = tree[rt<<1].r; int rl = tree[rt<<1|1].l, rr = tree[rt<<1|1].r; tree[rt].l = top+1; //下面排序是歸併排序的思想 while(ll<=lr && rl<=rr) { if(d[ll] <= d[rl]) d[++top] = d[ll++]; else d[++top] = d[rl++]; } while(ll <= lr) d[++top] = d[ll++]; while(rl <= rr) d[++top] = d[rl++]; tree[rt].r = top; } //二分查詢滿足的個數 int search(int l, int r, int k) { if(d[r] <= k) return r-l+1; if(d[l] > k) return 0; int m, ll = l, rr = r; while(ll < rr) { m = (ll+rr)>>1; if(d[m] > k) rr = m; else ll = m+1; } return ll-l; } //找到我們要詢問區間 如果總共有4個數, 查詢1~3, 我們就查詢1~2 和 3~3,這是要分開查的 int query(int L, int R, int k, int l, int r, int rt) { if(L<=l && r<=R) return search(tree[rt].l, tree[rt].r, k); int sum = 0; int m = (l+r)>>1; if(L <= m) sum += query(L, R, k, lson); if(R > m) sum += query(L, R, k, rson); return sum; } int main() { int n, m, sl, sr, x, y, k; while(~scanf("%d%d", &n, &m)) { top = 0; build(1, n, 1); while(m--) { scanf("%d%d%d", &x, &y, &k); sl = -inf, sr = inf; int sm, res; while(sl < sr) { sm = (sl+sr)>>1; res = query(x, y, sm, 1, n, 1); if(res >= k) sr = sm; else sl = sm + 1; } printf("%d\n", sl); } } return 0; }