【题解】 「FJOI2016」神秘数 线段树+倍增+离线 LOJ2174

Legend

Link \(\textrm{to LOJ}\)

一个可重复数字集合 \(S\)神秘数定义为最小的不能被 \(S\) 的子集的和表示的正整数

给定长 \(n\) 的正整数序列 \(a_i\)\(q\) 个询问,每次询问给定一个区间 \([l,r]\),求由 \(a_l,a_{l+1},\cdots,a_r\) 所构成的可重复数字集合的神秘数。

\(n \le 10^5,\sum a_i \le 10^9\)

Editorial

这题还挺有意思的。

首先考虑最暴力的做法:把区间提取出来 \(\rm{sort}\) 一遍。

然后从小到大贪心,如果当前能表示出 \(s\),下一个数字是 \(x\)

  • \(x>s+1\),答案就是 \(s\)
  • \(x \le s+1\),令 \(s \gets s + x\),重复上述操作。

如何优化暴力呢?想到以 \(2^k\) 为长度倍增。

\(a_j \in [2^i,2^{i+1})\) 的数字开同一棵线段树(下标是在原数组中的位置),维护区间最小值(没有数字认为是 \(\infty\))。

这样子对值域进行倍增,则当前的 \(s+1\) 总会落在恰好一个长度区间内,假设是 \([2^k,2^{k+1})\)

设询问是 \([l,r]\) 则检查值域为 \([2^k,2^{k+1})\) 的线段树里下标 \([l,r]\) 的最小值是否 \(\le s+1\)

如果 \(\le s+ 1\),那么这一棵线段树里的所有值都可以被拿走,加入到 \(s\) 里,因为拿走最小值之后 \(s+1\) 一定到了下一个值域区间。

反之 \(s\) 不能再被更新了,当前 \(s\) 即为答案。

所以复杂度即为 \(O(n \log n \log \sum a)\)

Code

人丑常数大呀。。。

最开始开了 \(30\) 个线段树果断 \(\rm{MLE}\),于是就把询问全部离线下来做就 OK 了。

好像暴力重构线段树比较慢,\(\rm luogu\) 上卡着 \(\rm{1s}\) 时限过的。

#include <bits/stdc++.h>

#define LL long long

int read() {
	char k = getchar(); int x = 0;
	while(k < '0' || k > '9') k = getchar();
	while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
	return x;
}

const int MX = 1e5 + 23;
struct node {
	int l, r, mn;
	node *lch, *rch;
}*root;

void pushup(node *x) {
	x->mn = std::min(x->lch->mn, x->rch->mn);
}

int S[32][MX];
node *build(int l, int r ,int id) {
	node *x = new node;
	x->l = l ,x->r = r;
	if(l == r) {
		x->mn = S[id][l] ? S[id][l] : INT_MAX;
		x->lch = x->rch = nullptr;
	}
	else {
		int mid = (l + r) >> 1;
		x->lch = build(l, mid, id);
		x->rch = build(mid + 1, r, id);
		pushup(x);
	}
	return x;
}

void fresh(node *x,  int id){
	if(x->l == x->r) {
		x->mn = S[id][x->l] ? S[id][x->l] : INT_MAX;
	}
	else {
		fresh(x->lch, id);
		fresh(x->rch, id);
		pushup(x);
	}
}

int Qmin(node *x, int l, int r) {
	if(l <= x->l && x->r <= r) return x->mn;
	if(l <= x->lch->r && x->lch->r < r)
		return std::min(Qmin(x->lch, l, r), Qmin(x->rch, l, r));
	if(l <= x->lch->r) return Qmin(x->lch, l, r);
	return Qmin(x->rch, l, r);
}

struct QUERY{
	int l ,r;
	int Ans;
}Q[MX];

int main() {
	int n = read();
	for(int i = 1; i <= n; ++i) {
		S[0][i] = read();
	}

	int m = read();
	for(int i = 1; i <= m; ++i) {
		Q[i].l = read(), Q[i].r = read();
		Q[i].Ans = 0;
	}
	
	root = build(1 ,n ,1);
	for(int i = 1; i <= 30; ++i) {
		for(int j = 1; j <= n; ++j) {
			if((1 << (i - 1)) <= S[0][j] && S[0][j] < (1 << i)) {
				S[i][j] = S[0][j];
			}
			else {
				S[i][j] = 0;
			}
		}
		fresh(root ,i);
		for(int j = 1; j <= n; ++j) {
			S[i][j] += S[i][j - 1];
		}
		for(int j = 1; j <= m; ++j) {
			if(Q[j].Ans + 1 < (1 << (i - 1))) {
				continue;
			}
			if(Qmin(root ,Q[j].l ,Q[j].r) <= Q[j].Ans + 1) {
				Q[j].Ans += S[i][Q[j].r] - S[i][Q[j].l - 1];
			}
		}
	}

	for(int i = 1; i <= m; ++i){
		printf("%d\n" ,Q[i].Ans + 1);
	}
	return 0;
}
posted @ 2020-10-15 13:33  Imakf  阅读(121)  评论(0编辑  收藏  举报