题目大意
给定一个有 (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]) ,否则区间无法扩大。