Luogu P4587 神秘数

题目大意

给定一个有 (n) 个正整数的可重集 (S) ,有 (m) 次询问,格式为 (l_i,r_i) ,要求输出集合 (left{a_j | a_j in S,j in [l_i,r_i] ight}) 的子集和不能构成最小正整数。
其中 (1 leq n leq 10^5)(1 leq m leq 10^5)(sum_{i = 1}^{n} a_i leq 10^9)

题解

假设此时已有若干个数可以构成的数的区间为 ([1,p])
此时若再给一个数 (q) ,容易证明,当 (q leq p + 1) 时,该区间可以变为 ([1,p+q]) ,否则不能。

对于每一个询问,我们对 ([l_i,r_i]) 中的数从小到大进行排序,并从小到大开始遍历。
若此时前 (j - 1) 个数可表示的区间为 ([1,p]),如果存在 (a_j,a_{j+1},dots,a_k) 满足不大于 (p+1) 的条件,则该区间可以扩大到 ([1,p+sum_{r=j}^{k}a_r]) 。若不存在,即 (sum_{r=j}^{k}a_r = 0),则答案为 (p + 1)
此次遍历结束后,下一次遍历可以找大小在 ([p+1+1,p+sum_{r=j}^{k}a_r + 1]) 的范围内的数。
即若这次遍历的数区间为 ([x+1,y+1]),可表示的数的范围由 ([1,y]) 变为 ([1,y+z]),则下一次遍历的数的区间为 ([y+2,y+z+1])
很显然套主席树即可。

#include <cstdio>
#include <algorithm>

using std::max;
using std::min;

#define MAX_N (100000 + 5)
#define MAX_M (100000 + 5)
#define MAX_SIZE 3400000
#define INF 1000000000

#define SUBMIT

#ifdef SUBMIT

#define SIZE (1 << 21) 
#define Getchar() (pr1 == pr2 && (pr2 = (pr1 = fr) + fread(fr, 1, SIZE, stdin), pr1 == pr2) ? EOF : *pr1++) 

char fr[SIZE], * pr1 = fr, * pr2 = fr;

#else

#define Getchar() getchar()

#endif

struct Tree
{
    int sum;
    int l, r;
};

int n, m;
int root[MAX_M], num;
Tree s[MAX_SIZE];

int Read();
void Insert(int, int, int, int, int);
int Query(int, int, int, int, int, int);

int main()
{
	n = Read();
	for (int i = 1; i <= n; ++i)
	{
		root[i] = ++num;
		Insert(root[i - 1], root[i], 1, INF, Read());
	}
	m = Read();
	int l, r, x, y, sum;
	while (m--)
	{
		l = Read();
		r = Read();
		x = 1;
		y = 0;
		while (1)
		{
			sum = Query(root[l - 1], root[r], 1, INF, x, y + 1);
			if (!sum) break;
			x = y + 2;
			y += sum;
		}
		printf("%d
", y + 1);
	}
    return 0;
}

int Read() 
{
    int res = 0;
    char ch = Getchar();
    while(ch < '0' || ch > '9')
	{
		ch = Getchar();
	}
    while(ch >= '0' && ch <= '9') 
    {
        res = res * 10 + ch - '0';
        ch = Getchar();
    }
    return res;
}

void Insert(int i1, int i2, int L, int R, int val)
{
	if (L == R)
	{
		s[i2].sum = s[i1].sum + val;
		return;
	}
	int mid = L + R >> 1;
	if (val <= mid)
	{
		s[i2].l = ++num;
		s[i2].r = s[i1].r;
		Insert(s[i1].l, s[i2].l, L, mid, val);
	}
	else
	{
		s[i2].l = s[i1].l;
		s[i2].r = ++num;
		Insert(s[i1].r, s[i2].r, mid + 1, R, val);
	}
	s[i2].sum = s[s[i2].l].sum + s[s[i2].r].sum;
}

int Query(int i1, int i2, int L, int R, int lt, int rt)
{
	if (lt <= L && R <= rt) return s[i2].sum - s[i1].sum;
    int mid = L + R >> 1, res = 0;
    if (max(L, lt) <= min(mid, rt) && s[s[i2].l].sum - s[s[i1].l].sum)
    {
		res += Query(s[i1].l, s[i2].l, L, mid, lt, rt);
    }
    if (max(mid + 1, lt) <= min(R, rt) && s[s[i2].r].sum - s[s[i1].r].sum)
    {
    	res += Query(s[i1].r, s[i2].r, mid + 1, R, lt, rt);
    }
    return res;
}

当然,还有另一种做法,若可表示数的范围为 ([1,y]) ,则在大小为 ([1,y]) 的范围内找数,若他们的和 (sum geq y) ,则区间可变为 ([1,sum+1]) ,否则区间无法扩大。

原文地址:https://www.cnblogs.com/kcn999/p/13444986.html