2020ICPC昆明区域赛M. Stone Games题解
M. Stone Games
题意
有一个长度为\(n\)的序列\(s_1,s_2,\dots,s_n\),\(Q\)次询问,每次询问包含一个区间\([l,r]\),问最小的不能通过选择\(s_l,s_{l+1},\dots,s_r\)中的若干数求和(可以不选,此时和为\(0\))所得到的数。强制在线。
-
\(n\leq 10^6\)
-
\(1\leq s_i\leq 10^9\)
分析
容易发现对于一个询问的区间,区间里的数的顺序与答案无关,所以可以先考虑对于一个不减序列\(a_1,a_2,\dots,a_n(a_i\geq1)\),如何得到答案。
设\(S_i=\sum\limits_{j=1}^{i}a_j\)。
假设对于\(a_1,a_2,\dots,a_i\),都有办法选择若干个数求和得到\(1,2,3,\dots,S_i\),可以尝试寻找\(a_{i+1}\)应满足什么条件,才能使得对于\(a_1,a_2,\dots,a_{i+1}\),都有办法选择若干个数求和得到\(1,2,3,\dots,S_{i+1}\)。
容易发现,条件是\(a_{i+1}\leq S_i+1\),因为对于\(1,2,3,\dots,S_i\),都可以在前\(i\)个数里选择得到;而对于\(S_i+1,S_i+2,\dots,S_{i}+a_{i+1}\),如果\(a_{i+1}>S_i+1\),\(S_i+1\)就无论如何也无法得到;如果\(a_{i+1}\leq S_i+1\),\(S_i+1,S_i+2,\dots,S_{i}+a_{i+1}\)可以表示为\((S_i+1-a_{i+1})+a_{i+1},(S_i+2-a_{i+1})+a_{i+1},\dots,(S_i+a_{i+1}-a_{i+1})+a_{i+1}\),其中括号里的数是可以在前\(i\)个数里选择得到的。
为了方便描述,令\(a_{n+1}=+\infty,S_0=0\),所以对于上面的问题就是寻找最小的\(k\)使得\(a_{k+1}>S_k+1\),此时答案为\(S_k+1\)。
问题就在于如何寻找上述的\(k\)。
一个暴力的想法是,\(k\)从\(0\)开始,线性的扫描,直到第一次找到符合条件的\(k\),这样解决这个问题的复杂度是\(O(n)\)的,不能满足多组询问的需要。
能不能二分查询呢,虽然\(a_i\)是单调不减的,\(S_i\)是单调递增的,但是\(a_{i+1}-S_i\)并不具有单调性,所以也不能二分。
但是\(S_i\)毕竟是递增的,如果当你发现对于任意的\(i\leq t\),都满足\(a_{i+1}\leq S_i+1\)时,那么符合条件\(k\)至少应该满足\(a_k>S_t+1\),换句话说满足\(a_j\leq S_t+1\)的\(j\)都不用考虑了。
所以可以考虑这样的优化,如果对于当前的\(i\)满足\(a_{i}\leq S_{i-1}+1\),我们下一个应该考察的是第一个大于\(S_{i-1}+1\)的\(a_j(j>i)\),判断\(a_j\)与\(S_{j-1}+1\)的关系,而不是简单的将\(i\)自增。如果\(a_j>S_{j-1}+1\)答案就找到了,否则继续上述操作。
问题是这样的优化能优化多少呢?
如果仍然有\(a_j\leq S_{j-1}+1\),设下一个考察的是第一个大于\(S_{j-1}+1\)的\(a_k(k>j)\),因为\(a_j>S_{i-1}+1\),所以此时有\(S_{k-1}+1=S_{i-1}+a_i+\dots+a_j+\dots+1>2S_{i-1}+2+a_i+\cdots>2(S_{i-1}+1)\),会发现每向后考察\(2\)个有必要考察的元素,\(S_{i-1}+1\)至少会扩大\(2\)倍,而每次要找的\(a_j\)都要大于某个\(S_{i-1}+1\),这样最多找\(2\left\lceil\log_2 \max\limits_{1\leq i\leq n} a_i\right\rceil\)次。而对于每次都要找第一个大于\(S_{i-1}+1\)的\(a_j\),这个利用二分可以在\(O(\log n)\)的复杂度内实现。
回到原问题,对于一个区间,这个子序列不一定是单调不减的,我们需要快速计算前\(i\)小的和\(S_i\),利用主席树(权值线段树的可持久化)做到\(O(\log n)\)查询,我们还需要快速计算第一个大于\(S_{i-1}+1\)的\(a_j\),利用二分可以\(O(\log n)\)求得(只需要二分整个区间上的数,因为就算当前区间外有这个数,而当前区间里没有这个数,主席树也是可以计算区间内小于等于某个数的和,这样最多会重复算几次一样的结果,不影响正确性和渐进复杂度)。这样这个问题就可以在\(O(n\log n+Q\log n\log \max\limits_{1\leq i\leq n} a_i)\)的复杂度内解决。
代码
#include <algorithm>
#include <cstdio>
#include <unordered_map>
using namespace std;
typedef long long Lint;
const int maxn = 1e6 + 10;
const int maxq = 1e5 + 10;
int n, Q;
int s[maxn], s1[maxn];
unordered_map<int, int> M;
struct Node {
Lint num;
Node *ls, *rs;
};
struct SegTree {
Node nodes[maxn * 50];
int tot_rt;
void init(Node*& rt) {
nodes[0].ls = nodes[0].rs = nodes;
nodes[0].num = 0;
rt = nodes;
}
void update(Node*& rt, Node* ori_rt, int l, int r, int pos, Lint val) {
rt = &nodes[++tot_rt];
rt->ls = rt->rs = &nodes[0];
if (l == r) {
rt->num = ori_rt->num + val;
return;
}
int mid = l + r >> 1;
if (pos <= mid) {
update(rt->ls, ori_rt->ls, l, mid, pos, val);
rt->rs = ori_rt->rs;
} else {
update(rt->rs, ori_rt->rs, mid + 1, r, pos, val);
rt->ls = ori_rt->ls;
}
rt->num = rt->ls->num + rt->rs->num;
}
Lint query(Node* l_rt, Node* r_rt, int l, int r, int L, int R) {
if (L <= l && r <= R)
return r_rt->num - l_rt->num;
int mid = l + r >> 1;
Lint ans = 0;
if (L <= mid)
ans += query(l_rt->ls, r_rt->ls, l, mid, L, R);
if (R > mid)
ans += query(l_rt->rs, r_rt->rs, mid + 1, r, L, R);
return ans;
}
} seg;
Node* rt[maxn];
int main() {
scanf("%d%d", &n, &Q);
for (int i = 1; i <= n; i++) {
scanf("%d", s + i);
s1[i] = s[i];
}
sort(s1 + 1, s1 + 1 + n);
int s1_tot = unique(s1 + 1, s1 + 1 + n) - s1 - 1;
for (int i = 1; i <= s1_tot; i++)
M[s1[i]] = i;
seg.init(rt[0]);
for (int i = 1; i <= n; i++)
seg.update(rt[i], rt[i - 1], 1, s1_tot, M[s[i]], s[i]);
Lint ans = 0;
while (Q--) {
int l, r;
scanf("%d%d", &l, &r);
l = (l + ans) % n + 1;
r = (r + ans) % n + 1;
if (l > r)
swap(l, r);
ans = 1;
while (1) {
int p = upper_bound(s1 + 1, s1 + 1 + s1_tot, ans) - s1;
Lint sum;
if (p == 1)
sum = 0;
else
sum = seg.query(rt[l - 1], rt[r], 1, s1_tot, 1, p - 1);
ans = sum + 1;
if (p == s1_tot + 1 || s1[p] > ans)
break;
}
printf("%lld\n", ans);
}
return 0;
}