比赛时想出来的做法,我并不会主席树,比赛时想到这个做法时非常激动,可惜没有A出来
题意:给一个长度为n数列a[n],m次查询,每次查询给一个区间[Li ,Ri],定义一个集合Si,Si为这个区间里选任意一些数相加得到的数,定义f(S)为集合S里没出现的最小的正数,对于每次查询,输出f(Si)
示例:
1 4 1 2 6
区间为[1,3]时,构成的集合S为{1,2,4,5,6},那么f(S)为3
数据范围:1<= n <=1e6 ; 1<=m <= 1e5 ; 1<=a[i]<=1e9;
给定一个区间后,怎么求f(S)?
研究了一段时间后,可以发现f(S)能这样求,首先把区间里的数排序,f(S)先初始化为1,看最小的数是否>1,如果>1,那么f(S)就为1,如果==1,那么f(S)就加上1,然后再看第二小的数是否>f(S),如果<=f(S),f(S)就加上这个数,如果>f(S),那么f(S)就确定了
也就是f(S)可以被所有小于等于f(S)的数之和+1更新
写成代码的话就是这样:(mex为f(S))
int mex = 1;
while (1) {
int res = 区间里所有小于等于mex的数之和+1
if (res <= mex) break;
mex = res;
}
但是查询有多次,我们不能用排序,否则会改变数组,所以要想办法快速求出一段区间里所有小于等于x的数之和
这个可以用主席树来求
但是比赛时我不会主席树,就一直在想怎么求这个
我的做法用线段树来求一段区间里所有小于等于1,小于等于2,小于等于4,小于等于8.....小于等于2^30的数之和,一个NODE里的sum[i]表示区间里所有小于等于(1<<i)的数之和
假设我现在mex通过所有小于等于512的数之和(记为low(512))更新到1400,这样当我要求low(1400)时,我可以先用low(1024)来近似,如果low(1024)>=2048,那么我就可以用low(2048)来更新mex了,
那么如果low(1024)<2048呢,我怎么求low(1024)+1024到1400的数之和呢
我们还可以求出一段区间里所有大于1024的最小的数,记为min(1024),如果min(1024) > 1400,那么说明1024到1400之间不存在任何数,如果min(1024)<1400,那么1024到1400之间至少存在一个数,而low(1024) = 1400只要加上这个数,就一定会大于2048,所以就可以用low(2048)来更新mex了!!
NODE结构体是这样的
struct NODE {
int l, r;
long long su[31], mi[31];//su[i]表示所有小于等于(1<<i)的数之和, mi[i]表示所有大于(1<<i)的数之和
}tree[MAXN*4];
赛后AC代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int MAXN = 1e6 + 7;
const long long INF = 1e9 + 7;
struct NODE {
int l, r;
long long su[31], mi[31];
}tree[MAXN*4];
void upd(int pos) {
for (int i = 0; i < 31; i++) {
tree[pos].su[i] = tree[pos << 1].su[i] + tree[pos << 1 | 1].su[i];
tree[pos].mi[i] = min(tree[pos << 1].mi[i], tree[pos << 1 | 1].mi[i]);
}
}
void build(int pos, int l, int r) {
tree[pos].l = l; tree[pos].r = r;
if (l == r) {
long long v;
scanf("%lld",&v);
for (int i = 0; i < 31; i++) {
if (v > ((long long)1 << i)) {
tree[pos].su[i] = 0;
}
else tree[pos].su[i] = v;
}
for (int i = 0; i < 31; i++) {
if (v <= ((long long)1 << i)) tree[pos].mi[i] = INF;
else tree[pos].mi[i] = v;
}
return;
}
int mid = l + r >> 1;
build(pos<<1, l, mid);
build(pos<<1|1, mid + 1, r);
upd(pos);
}
long long Q(int pos, int l, int r, int i) {
if (tree[pos].l == l && tree[pos].r == r) return tree[pos].su[i];
int mid = tree[pos].l + tree[pos].r >> 1;
if (r <= mid) return Q(pos<<1, l, r, i);
else if (l > mid) return Q(pos<<1|1, l, r, i);
else return Q(pos<<1, l, mid, i) + Q(pos<<1|1, mid + 1, r, i);
}
long long Q_mi(int pos, int l, int r, int i) {
if (tree[pos].l == l && tree[pos].r == r) return tree[pos].mi[i];
int mid = tree[pos].l + tree[pos].r >> 1;
if (r <= mid) return Q_mi(pos << 1, l, r, i);
else if (l > mid) return Q_mi(pos << 1 | 1, l, r, i);
else return min(Q_mi(pos << 1, l, mid, i), Q_mi(pos << 1|1, mid + 1, r, i));
}
int main()
{
int n, m;
cin >> n >> m;
build(1, 1, n);
int l, r;
long long ans, mex;
for (int i = 1; i <= m; i++) {
scanf("%d%d", &l, &r);
l = ((long long)l + ans) % n + 1;
r = ((long long)r + ans) % n + 1;
if (l > r) swap(l, r);
mex = 1;
for (int i = 0; i < 31; i++) {
if (mex >= ((long long)1 << i)) mex = max(mex, Q(1, l, r, i) + 1);
else if(i){
long long lim = Q_mi(1, l, r, i - 1);
if (lim != INF && lim > mex) break;
else {
mex = max(mex, Q(1, l, r, i) + 1);
}
}
}
ans = mex;
printf("%lld
", ans);
}
return 0;
}
至于比赛时我为什么没能过这题,原因是爆空间了QAQ,1个G的内存给我MLE了。。。
比赛时我用的是指针写法的线段树,NODE节点里有比较多东西阿巴阿巴。。