【GDOI2018模拟8.12】区间第k小

Description:

这里写图片描述

Data Constraint:

这里写图片描述

题解:

对于w=100000,相当于没有w这个限制,直接主席树即可。
对于非强制在线的,可以考虑莫队算法,对值域分块,维护每个块小于等于w的数的个数,查询时暴力过去,直到发现答案在当前这个块里,停下来,再暴力,一次是O( n )。
但是现在强制在线了,how to do it?

还是可以暴力分块的。
对序列也分块。
设c[i][j][k]表示序列上的第i块到第j块在值域的第k块的数的个数(减去超过w次的)。
可以 O(n1.5) 预处理.
再设s[i][j]表示序列上的前i块中值为j的有多少个。

对于每一个询问[l..r],我们可以借助辅助数组c、s在 O(n) 的时间里求出b[i],其中b[i]表示值域第i块的在区间[l..r]li一共有多少个出现次数不超过w的数,于是我可以暴力扫过去,直到发现答案在当前块里,再暴力,此时在借助辅助数组s,可以求出每个数出现了多少次。

总复杂度是 O(n1.5) 的,空间也是这么大的。

Code:

#include<cstdio>
#include<cstring>
#define fo(i, x, y) for(int i = x; i <= y; i++)
#define min(a, b) ((a) < (b) ? (a) : (b))
using namespace std;

const int N = 100005, M = 320;

int n, w, type, ans, Q, x, y, z, a[N], c[M][M][M], s[M][N], t[N], b[M], bz[N];
int num[N], l[M], r[M];

void Init() {
    scanf("%d %d %d %d", &n, &w, &Q, &type);
    w ++;
    fo(i, 1, n) scanf("%d", &a[i]);
    fo(i, 0, n) {
        num[i] = num[i - 1] + (i % M == 0);
        if(i % M == 0) {
            l[num[i]] = i;
            r[num[i - 1]] = i - 1;
        }
    }
    r[num[n]] = n;
}

void Build() {
    fo(i, 1, num[n]) {
        memset(t, 0, sizeof(t));
        fo(j, i, num[n]) {
            if(j != i) fo(k, 1, num[n]) c[i][j][k] = c[i][j - 1][k];
            fo(k, l[j], r[j]) {
                t[a[k]] ++;
                if(t[a[k]] <= w) {
                    c[i][j][num[a[k]]] ++;
                    if(t[a[k]] == w) c[i][j][num[a[k]]] -= w;
                }
            }
        }
    }
    fo(i, 1, num[n]) {
        fo(j, 0, n) s[i][j] = s[i - 1][j];
        fo(j, l[i], r[i]) s[i][a[j]] ++;
    }
}

void xiu(int i) {
    if(!bz[a[i]]) {
        bz[a[i]] = 1;
        if(num[x] + 1 <= num[y] - 1)
            t[a[i]] = s[num[y] - 1][a[i]] - s[num[x]][a[i]];
    }
    t[a[i]] ++;
    if(t[a[i]] <= w) {
        b[num[a[i]]] ++;
        if(t[a[i]] == w) b[num[a[i]]] -= w;
    }
}

void End() {
    memset(t, 0, sizeof(t));
    for(; Q; Q --) {
        scanf("%d %d %d", &x, &y, &z);
        x ^= ans * type; y ^= ans * type; z ^= ans * type;
        ans = n;
        fo(i, 1, num[n]) b[i] = c[num[x] + 1][num[y] - 1][i];
        if(num[x] != num[y]) {
            fo(i, x, r[num[x]]) xiu(i);
            fo(i, l[num[y]], y) xiu(i);
        } else {
            fo(i, x, y) xiu(i);
        }
        fo(i, 1, num[n]) {
            z -= b[i];
            if(z <= 0) {
                z += b[i];
                fo(j, l[i], r[i]) {
                    if(!bz[j]) {
                        bz[j] = 1;
                        if(num[x] + 1 <= num[y] - 1)
                            t[j] = s[num[y] - 1][j] - s[num[x]][j];
                    }
                    if(t[j] < w) z -= t[j];
                    if(z <= 0) {
                        ans = j;
                        fo(k, l[i], j) bz[k] = t[k] = 0;
                        break;
                    }
                }
                break;
            }
        }
        fo(i, x, r[num[x]]) bz[a[i]] = t[a[i]] = 0;
        fo(i, l[num[y]], y) bz[a[i]] = t[a[i]] = 0;
        printf("%d\n", ans);
    }
}
int main() {
    freopen("kth.in", "r", stdin);
    freopen("kth.out", "w", stdout);
    Init();
    Build();
    End();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值