poj 2104 K-th Number 区间第K大 二分 离散化 + (莫队 树状数组/平方分解/线段树)

博客详细介绍了POJ 2104题目的解决方案,包括使用莫队算法配合树状数组、平方分解以及线段树的解法。离散化数据后,通过莫队算法可以在较短时间内找到答案,而平方分解方法适合处理部分数据,线段树则提供了一种在树结构上进行区间查询的手段。三种方法各有优缺点,如莫队算法快速但代码较长,线段树代码简洁但效率稍低。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

题目链接:http://poj.org/problem?id=2104

题目来源:《挑战》例题。

简要题意:求区间第 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。

需要注意的是右边的区间应该继续往下减而不是停留在模 B 0

否则最后一块的第一个元素会漏处理,可以处理成左闭右开。

平方分解代码

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值