1. 程式人生 > 其它 >2020ICPC昆明區域賽M. Stone Games題解

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