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;
}
posted @ 2021-12-09 17:47  聆竹听风  阅读(144)  评论(0)    收藏  举报