POJ 2104 - 主席树 / 询问莫队+权值分块

传送门

题目大意应该都清楚。
今天看到一篇博客用分块+莫对做了这道题,直接惊呆了。
首先常规地离散化后将询问分块,对于某一询问,将莫队指针移动到指定区间,移动的同时处理权值分块的数字出现次数(单独、整块),莫队完后,现在的权值分块就是关于当前区间的。然后再从左到右扫描分块,直到数字个数+该块个数>=k,这时进入该块逐个加,当数字个数>=k时,直接跳出输出离散化之前的数字。
试了很多种块的大小,最后还是选择sqrt(100000) ≈ 320,时间比我的主席树还少(肯定是我写的丑)。

code

1040ms

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<string>
#include<algorithm>
using namespace std;

const int N = 1e5 + 5, M = 5e3 + 5, S = 320;
int n, m;
int val[N], b[N], len, cnt[S], num_cnt[N], ans[M];
struct QRY{
    int x, y, k, mo_bl, id;
    inline bool operator < (const QRY &b) const{
        return mo_bl < b.mo_bl || (mo_bl == b.mo_bl && y < b.y);
    }
}qry[M];
int head, tail;

namespace IO{
    inline int read(){
        int i = 0, f = 1; char ch = getchar();
        for(; (ch < '0' || ch > '9') && ch != '-'; ch = getchar());
        if(ch == '-') f = -1, ch = getchar();
        for(; ch >= '0' && ch <= '9'; ch = getchar()) i = (i << 3) + (i << 1) + (ch - '0');
        return i * f;
    }
    inline void wr(int x){
        if(x < 0) putchar('-'), x = -x;
        if(x > 9) wr(x / 10);
        putchar(x % 10 + '0');
    }
}using namespace IO;

inline void disc_init(){
    sort(b + 1, b + len + 1);
    len = unique(b + 1, b + len + 1) - b - 1;
    for(int i = 1; i <= n; i++) val[i] = lower_bound(b + 1, b + len + 1, val[i]) - b;
}

inline void handleMo(int l, int r){
    while(head < l){
        int part_bl = val[head] / S + (val[head] % S ? 1 : 0);
        cnt[part_bl]--;
        num_cnt[val[head]]--;
        head++;
    }
    while(head > l){
        head--;
        int part_bl = val[head] / S + (val[head] % S ? 1 : 0);
        cnt[part_bl]++;
        num_cnt[val[head]]++;
    }
    while(tail < r){
        tail++;
        int part_bl = val[tail] / S + (val[tail] % S ? 1 : 0);
        cnt[part_bl]++;
        num_cnt[val[tail]]++;
    }
    while(tail > r){
        int part_bl = val[tail] / S + (val[tail] % S ? 1 : 0);
        cnt[part_bl]--;
        num_cnt[val[tail]]--;
        tail--;
    }
}
int main(){
    freopen("h.in", "r", stdin);
    n = read(), m = read();
    for(int i = 1; i <= n; i++) val[i] = b[++len] = read();
    disc_init();
    for(int i = 1; i <= m; i++){
        int x = read(), y = read(), k = read(), bl = x / S + (x % S ? 1 : 0);
        qry[i] = (QRY){x, y, k, bl, i};
    }
    sort(qry + 1, qry + m + 1);
    head = tail = 0;
    for(int i = 1; i <= m; i++){
        int l = qry[i].x, r = qry[i].y, k = qry[i].k, sum = 0;
        handleMo(l, r);
        int now = 1;
        try{
            while(sum + cnt[now] < k) sum += cnt[now++];
            for(int j = (now - 1) * S + 1; j <= min(n, now * S); j++)
                if(sum + num_cnt[j] < k) sum += num_cnt[j];
                else throw(j);
        }
        catch(int x){
            ans[qry[i].id] = b[x];
            continue;
        }
    }
    for(int i = 1; i <= m; i++) wr(ans[i]), putchar('\n');
    return 0;
}

转载于:https://www.cnblogs.com/CzYoL/p/7657025.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值