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节点里有比较多东西阿巴阿巴。。

 

posted @ 2021-04-06 00:12  beta_dust  阅读(76)  评论(0编辑  收藏  举报