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_{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;
}