2021ICPC昆明M题 非主席树做法
比赛时想出来的做法,我并不会主席树,比赛时想到这个做法时非常激动,可惜没有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\n", ans); } return 0; }
至于比赛时我为什么没能过这题,原因是爆空间了QAQ,1个G的内存给我MLE了。。。
比赛时我用的是指针写法的线段树,NODE节点里有比较多东西阿巴阿巴。。