1. 程式人生 > >poj 2104 K-th Number 區間第K大 二分 離散化 + (莫隊 樹狀陣列/平方分解/線段樹)

poj 2104 K-th Number 區間第K大 二分 離散化 + (莫隊 樹狀陣列/平方分解/線段樹)

題目

題解

比較經典的題目,我用了三個方法來寫。

其中第一種第三種我覺得比較好寫,第二種出現了各種問題。

總的來說第一種速度快,但是程式碼長,第三種速度慢一些,但是程式碼比較短,第二種程式碼和第三種差不多,但是慢了很多,寫起來很蛋疼。

第一種是我自己寫的寫法,後兩種是書上的寫法。好像還可以用其他亂七八糟樹來弄。

題解1

首先可以離散化一下資料。

然後莫隊去搞,然後用樹狀陣列來維護整個個數,再去二分答案。

整個程式碼比較長,不過寫起來還是比較方便的。

莫隊+樹狀陣列程式碼

#include <iostream>
#include <cstdio> #include <cmath> #include <algorithm> #include <cstring> #include <stack> #include <queue> #include <string> #include <vector> #include <set> #include <map> #define fi first #define se second using namespace std; typedef long
long LL; typedef pair<int,int> PII; // head const int N = 1e5+5; const int M = 5e3+5; const int B = 1000; struct BIT { #define T int T tree[N] ; inline int lowbit(int x) { return x&(-x); } void add(int x, T add, int n) { for (int i = x; i <= n; i += lowbit(i)) { tree[i] += add; } } T sum(int
x) { T ans = 0; for (int i = x; i > 0; i -= lowbit(i)) { ans += tree[i]; } return ans; } T query(int k, int n) { int l = 1, r = n, ans = n; while (l <= r) { int mid = (l + r) / 2; if (sum(mid) >= k) { ans = mid; r = mid-1; } else { l = mid+1; } } return ans; } void clear(int n) { for (int i = 1; i <= n; i++) { tree[i] = 0; } } #undef T }; BIT bt; struct Query { int l, r, k, id; }; bool cmp(const Query &a, const Query &b) { int apos = a.l / B, bpos = b.l / B; return apos == bpos ? a.r < b.r : apos < bpos; } Query query[M]; int a[N]; int dis[N]; int res[M]; void add(int x, int n) { bt.add(x, 1, n); } void rem(int x, int n) { bt.add(x, -1, n); } int main() { int n, m; while (scanf("%d%d", &n, &m) == 2) { for (int i = 1; i <= n; i++) { scanf("%d", a+i); dis[i-1] = a[i]; } sort(dis, dis + n); int tot = unique(dis, dis + n) - dis; for (int i = 1; i <= n; i++) { a[i] = lower_bound(dis, dis + tot, a[i]) - dis + 1; } for (int i = 0; i < m; i++) { scanf("%d%d%d", &query[i].l, &query[i].r, &query[i].k); query[i].id = i; } sort(query, query + m, cmp); int l = 1, r = 0; for (int i = 0; i < m; i++) { while (r < query[i].r) add(a[++r], tot); while (l > query[i].l) add(a[--l], tot); while (r > query[i].r) rem(a[r--], tot); while (l < query[i].l) rem(a[l++], tot); res[query[i].id] = bt.query(query[i].k, tot); } for (int i = 0; i < m; i++) { printf("%d\n", dis[res[i]-1]); } bt.clear(tot); } return 0; }

題解2

然後我們也可以把整個東西劃分成約n個塊,每塊內排序。

對於完整在裡面的一塊,可以二分,兩邊的則直接暴力for。

需要注意的是右邊的區間應該繼續往下減而不是停留在模B0

否則最後一塊的第一個元素會漏處理,可以處理成左閉右開。

平方分解程式碼

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <stack>
#include <queue>
#include <string>
#include <vector>
#include <set>
#include <map>
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
// head
const int N = 1e5+5;
const int B = 1000;

vector<int> b[N/B + 5];
int a[N], c[N];

int get(int l, int r, int x) {
    int ans = 0;
    while (l < r && l % B) {
        if (a[l++] <= x) ans++;
    }
    while (l < r && r % B) {
        if (a[--r] <= x) ans++;
    }
    for (int i = l; i < r; i += B) {
        int pos = i / B;
        ans += upper_bound(b[pos].begin(), b[pos].end(), x) - b[pos].begin();
    }
    return ans;
}

int query(int L, int R, int k, int n) {
    int l = 0, r = n-1, ans;
    while (l <= r) {
        int mid = (l + r) / 2;
        int cur = get(L, R, c[mid]);
        if (cur >= k) {
            ans = mid;
            r = mid-1;
        } else {
            l = mid+1;
        }
    }
    return c[ans];
}

int main() {
    int n, m, x, l, r, k;
    while (scanf("%d%d", &n, &m) == 2) {
        for (int i = 0; i < n; i++) {
            scanf("%d", a+i);
            c[i] = a[i];
            b[i/B].push_back(a[i]);
        }
        sort(c, c + n);
        for (int i = 0; i <= n/B; i++) {
            sort(b[i].begin(), b[i].end());
        }

        for (int i = 0; i < m; i++) {
            scanf("%d%d%d", &l, &r, &k);
            l--;
            printf("%d\n", query(l, r, k, n));
        }

        for (int i = 0; i <= n/B; i++) {
            b[i].clear();
        }
    }
    return 0;
}

題解3

然後可以建一顆歸併樹,在樹上找到區間然後去二分。

具體就是每個節點儲存一個升序的陣列,然後不斷merge。

線段樹程式碼

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <stack>
#include <queue>
#include <string>
#include <vector>
#include <set>
#include <map>
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
// head
const int N = 1e5+5;

int a[N];
struct SegmentTree {
#define lson (rt<<1)
#define rson ((rt<<1)|1)
#define MID ((L+R)>>1)
#define lsonPara lson, L, MID
#define rsonPara rson, MID+1, R
#define all(v) v.begin(), v.end()

    const static int TN = N << 2;

    vector<int> t[TN];

    void pushUp(int rt, int L, int R) {
        t[rt].resize(R - L + 1);
        merge(all(t[lson]), all(t[rson]), t[rt].begin());
    }

    void build(int rt, int L, int R) {
        if (L == R) {
            t[rt].resize(1);
            scanf("%d", a+L);
            t[rt][0] = a[L];
        } else {
            build(lsonPara);
            build(rsonPara);
            pushUp(rt, L, R);
        }
    }

    int query(int rt, int L, int R, int l, int r, int x) {
        if (l > R || r < L || l > r) return 0;
        if (l <= L && r >= R) {
            int ans = upper_bound(all(t[rt]), x) - t[rt].begin();
            return ans;
        }
        return query(lsonPara, l, r, x) + query(rsonPara, l, r, x);
    }
};

SegmentTree st;
int main() {
    int n, m, L, R, k;
    while (scanf("%d%d", &n, &m) == 2) {
        st.build(1, 1, n);
        sort(a + 1, a + n + 1);
        int tot = unique(a + 1, a + n + 1) - a;
        while (m--) {
            scanf("%d%d%d", &L, &R, &k);
            int l = 1, r = tot-1, ans;
            while (l <= r) {
                int mid = (l + r) / 2;
                if (st.query(1, 1, n, L, R, a[mid]) >= k) {
                    ans = mid;
                    r = mid-1;
                } else {
                    l = mid+1;
                }
            }
            printf("%d\n", a[ans]);
        }
    }
    return 0;
}

相關推薦

poj 2104 K-th Number 區間K 二分 離散 + ( 陣列/平方分解/線段)

題目 題解 比較經典的題目,我用了三個方法來寫。 其中第一種第三種我覺得比較好寫,第二種出現了各種問題。 總的來說第一種速度快,但是程式碼長,第三種速度慢一些,但是程式碼比較短,第二種程式碼和第三種差不多,但是慢了很多,寫起

POJ 2104 K-th Number(區間k大數)(平方切割,歸並,劃分

ac代碼 deb rank turn tracking line 查看 div 能夠 題目鏈接: http://poj.org/problem?id=2104 解題思路: 由於查詢的個數m非常大。樸素的求法無法在規定時間內求解。因此應該選用合理的方式維護數據來做到高效

POJ 2985 The k-th Largest Group k大數 Treap / 陣列 + 並查集

題目連結 題意 有 n 只貓,m 次操作(n,m≤2e5): 0ij:將第 i 只貓所在組與第 j 只貓所在組合並; 1k:詢問第 k 大的組中有多少隻貓。 法一:Treap 參考資料 注意點 無需首先將所有組全部插入。可以在 treap

6231 K-th Number (2017CCPC哈爾濱站 二分+尺取法)

Alice are given an array A[1..N] with N numbers. Now Alice want to build an array B by a parameter K as following rules: Initially, the array B is empty

POJ 2104 K-th Number(主席區間K的數)

Description You are working for Macrohard company in data structures department. After failing your previous task about key insertion you

POJ 2104 K-th Number 主席(求區間k)

主席書資料 題意:給出n個數,m次詢問,[x,y]內第k小的數時多少?n<=1e5,m<=5000 主席樹:對原序列的每個字首i都建立一個線段樹 維護值域[l,r]中的每個數,在字首i的

POJ 2104 K-th Number ( 求取區間 K 值 )

二分法 esp size 麻煩 == 平方分割 closed push_back ret 題意 : 給出一個含有 N 個數的序列,然後有 M 次問詢,每次問詢包含 ( L, R, K ) 要求你給出 L 到 R 這個區間的第 K 大是幾 分析 : 求取區間 K 大值是

A - K-th Number POJ - 2104 -主席第一彈-K的數

感謝:http://www.cnblogs.com/zyf0163/p/4749042.html https://blog.csdn.net/qq_24451605/article/details/49031123 裸題多次查詢給定區間L—R內第K大的數 #include&

POJ 2104 K-th Number(主席

ber sca first n) 次數 example == scan sorted K-th Number Time Limit: 20000MS Memory Limit: 65536K Total Submissions: 5742

POJ 2104 K-th Number (主席)

std +++ esp space ctype == string uniq upd 題意:給定一個序列,然後有 q 個詢問,每次詢問 l - r 區間內的第 k 大的值。 析:很明顯的主席樹,而且還是裸的主席樹,先進行離散化,然後用主席樹進行查詢就好。 代碼如下: #p

[POJ 2104]K-th Number

n) 劃分樹 tput lease lap form 我們 歸並 nts Description You are working for Macrohard company in data structures department. After failing your

[poj 2104]主席+靜態區間k

include end 區間 得到 name int 題目 tar tdi 題目鏈接:http://poj.org/problem?id=2104 主席樹入門題目,主席樹其實就是可持久化權值線段樹,rt[i]維護了前i個數中第i大(小)的數出現次數的信息,通過查詢兩棵樹的差

POJ 2104 K-th Number

poj 2104 working lan 只需要 please lin absolut input nlogn Time Limit: 20000MS Memory Limit: 65536K Total Submissions: 5948

poj 2104主席區間k

區間 ++ cto ast http lan air algorithm while POJ - 2104 題意:求區間第k小 思路:無修改主席樹 AC代碼: #include "iostream" #include "iomanip" #include "string.

Poj 2104 K-th Number 主席模版題

OS tdi pda sig signed begin ostream air color 題意:離線詢問[l,r]區間第k大 題解:模版題,入門題 #include <iostream> #include <cstdio> #inclu

POJ-2104 K-th Number CDQ分治

col 分享圖片 pri 找到 oid play out else mes 題目傳送門 題意:給你一個序列,長度為n,m次詢問,詢問一段區間的第k大。 題解:CDQ分治,對整個值域進行分治。每次取一個mid, 計算出整個區間內mid <= 的數目,如果 num >

POJ 2104】【主席模板題】K-th Number

題意:       靜態詢問區間第K大問題。給出一個數組,然後多次詢問某一區間第K大數是多少。   思路:       典型的主席樹模板題。       所以就大致講一下靜態主席樹

K-th Number POJ - 2104 (主席 學習詳解)

https://cn.vjudge.net/problem/POJ-2104 題意 給你N個數 嗎、M次查詢,每次查詢給你 IJK 問第I個數到第J個數中第K大 思路 字典樹,每新增一個數都建立一棵線段樹,J和I 做減法就可以的到這個區間的線段樹 #include <c

歸併(POJ 2104 K-th Number)

        在求解區間第k個數的問題,除了劃分樹以外我們還可以使用另一種高效的方法 ------ 歸併樹。 1、演算法描述         所謂歸併樹,就是利用線段樹的建樹過程,將歸併排序的過程儲存。(不會線段樹:here,不會歸併排序:here)。在說明歸併樹之前我們

poj 2104 K-th Number (主席入門模板題)

摘抄了一段主席樹的解釋:所謂主席樹呢,就是對原來的數列[1..n]的每一個字首[1..i](1≤i≤n)建立一棵線段樹,線段樹的每一個節點存某個字首[1..i]中屬於區間[L..R]的數一共有多少個(比如根節點是[1..n],一共i個數,sum[root]