K-th Number POJ - 2104 平方分割/线段树

题目链接:K-th Number POJ - 2104

题目大意,大小为n的数列a1-an,m次询问,格式为i,j,k,意思是ai-aj中第k小的数。(n<1e5,m<5e3)

挑战程序设计竞赛上的两种解法。

核心思路都是一样的,设计某种一个数据结构,能快速求出i-j中小于等于x的元素个数,然后二分答案。

平方分割:
将n个元素分成sqrt(n)个部分,每个部分内部都拍好了序,这样一个完整部分内部小于等于x的元素个数可以直接用二分(upper_bound())求解,不完整部分也能在sqrt(n)时间内遍历求出。
但在poj上只有一定的概率能AC,TLE的可能性很大,按挑战上的说法,复杂度是O(nlogn+m*sqrt(n)*(logn)^1.5) 所以最大是1.8e8,限时20s,很悬。(submit了20多次才发现一下原来代码没问题,有一定概率TLE,害得我改了好久的代码)
AC(TLE)代码(AC概率相对TLE较小。。。)

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>

using namespace std;

const int MAXN = 2*1e5;
const int b = 1000;

int n, m, a[MAXN], num[MAXN], l, r, k;
vector<int> bucket[MAXN/b];

int cal(int l, int r, int x) //<=x[l,r]区间中<=x元素个数
{
    int tl = l, tr = r, ans = 0;
    while(tl < tr && tl%b) if(a[tl++] <= x) ++ans;//不完整部分,枚举
    while(tl < tr && tr%b) if(a[--tr] <= x) ++ans;

    while(tl<tr)//完整部分直接二分求
    {
        int bb = tl / b;
        ans += upper_bound(bucket[bb].begin(), bucket[bb].end(), x) - bucket[bb].begin();
        tl += b;
    }
    return ans;
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i=0; i<n; ++i)
    {
        scanf("%d", a+i);
        bucket[i/b].push_back(a[i]);
        num[i] = a[i];
    }
    sort(num, num+n);
    for(int i=0; i<n/b; ++i) sort(bucket[i].begin(), bucket[i].end());

    for(int i=0; i<m; ++i)
    {
        scanf("%d%d%d", &l, &r, &k);//好像现把全部数据一次性读入存下来在运算AC概率更大
        --l; --r;//题目数据下标从1开始,这个程序中从0开始
        int low = 0, high = n-1;//二分的是有序数组num的下标,而不是答案的值
        int mid;
        while(low < high)//二分
        {
            mid = (high + low)/2;
            int x = num[mid];
            if(cal(l, r+1, x) < k) low = mid+1;
            else high = mid;
        }
        printf("%d\n", num[low]);
    }

    return 0;
}

下面是一定能AC的线段树解法
和普通线段树类似,但每个节点都是一个有序数组这样每个区间都可分成多个子区间再对他们都进行二分求小于x的元素个数
复杂度是O(nlogn + m*(logn)^3)最多1e7

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>

using namespace std;

const int MAXN = 1e5 + 100, INF = 1e9*2;

int n, m, a[MAXN], I, J, K, num[MAXN];

vector<int> dat[MAXN<<2];

#define ls l, m, rt<<1
#define rs m+1, r, rt<<1|1
#define defm int m = (l+r)>>1

void pushUp(int rt)
{
    dat[rt].resize(dat[rt<<1].size()+dat[rt<<1|1].size());//挑战上没有这一句,能过编译但运行时会奔溃,加上这句就好了,因为dat[rt]的大小是0,merger似乎并不会让vector自动申请内存,所以吧dat[rt]的大小用resize设置成需要的大小就好了
    //merger合并两个有序序列成一个有序序列
    merge(dat[rt<<1].begin(), dat[rt<<1].end(), dat[rt<<1|1].begin(), dat[rt<<1|1].end(), dat[rt].begin());
}

void init(int l, int r, int rt)
{
    if(l == r)
    {
        dat[rt].push_back(a[l]);
        return;
    }
    defm;
    init(ls);
    init(rs);
    pushUp(rt);
}

int query(int L, int R, int x, int l, int r, int rt)
{
    if(R < l || L > r) return 0;//完全不在询问区间
    if(L <= l && r <= R) return upper_bound(dat[rt].begin(), dat[rt].end(), x) - dat[rt].begin();//完全在询问区间
    defm;
    return query(L, R, x, ls) + query(L, R, x, rs);//部分在询问区间
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i=1; i<=n; ++i) scanf("%d", a+i), num[i] = a[i];
    init(1, n, 1);
    sort(num+1, num+n+1);

    for(int i=0; i<m; ++i)
    {
        scanf("%d%d%d", &I, &J, &K);
        int low = 1, high = n, mid;

        while(low < high)
        {
            mid = (low + high)/2;
            if(query(I, J, num[mid], 1, n, 1) < K) low = mid + 1;
            else high = mid;
        }
        printf("%d\n", num[low]);
    }

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值