NowCoder 7605C Solution

题面

here

思路

考虑这个问题。

现有一个最小不能表示数为 \(x\) 的集合 \(S\)。问能够通过将其加入使得 \(x\) 改变的最大整数 \(y\) 是多少?

显然,\(y=x\)。因为,若 \(y\ge x+1\),则集合 \(S\) 的最小不能表示数仍然为 \(x\)

利用这个性质,我们可以在线段树上维护信息以解决问题。

解法

考虑以下线段树节点定义,并暂时忽略 operator+ 的内容。

struct st
{
    ll mx;
    multiset<pll> prep;
    st operator+(const st &x) const
    {
        st res = {mx + x.mx, {}};
        multiset<pll> tst;
        if (prep.size() >= x.prep.size())
        {
            tst = prep;
            for (auto &i : x.prep)
                tst.insert(i);
        }
        else
        {
            tst = x.prep;
            for (auto &i : prep)
                tst.insert(i);
        }
        while (tst.size() and res.mx >= tst.begin()->first)
        {
            res.mx += tst.begin()->second;
            tst.erase(tst.begin());
        }
        if (!tst.size())
            return res;
        ll req = tst.begin()->first, sm = 0;
        for (auto &[trq, v] : tst)
        {
            if (req + sm >= trq)
            {
                sm += v;
                continue;
            }
            res.prep.insert({req, sm});
            req = trq, sm = v;
        }
        res.prep.insert({req, sm});
        return res;
    }
};

其中,mx 为该节点所属区间内的最小不能表示数\(1\)

prep 所使用的内部数据类型为 pll,即 pair<ll,ll>,其内部信息形如(使 mx 能够增加“一个数”的最小 mx,“这个数”)。

prep 的解释

首先考虑单个数 \(x\) 的情况。

如果 \(x=1\),则仅包含 \(x\) 的集合的最小不能表示数为 \(2\)

否则,\(x>1\),仅包含 \(x\) 的集合的最小不能表示数为 \(1\)

与此同时,根据“解法”中提到的结论,我们可以知道,当这个集合的最小不能表达数增加至 \(x\) 及以上时,这个集合的最小不能表达数将会增加 \(x\)

有点绕,但就是这样的。

所以,存储这个信息有什么用呢?

实际上,这个信息仅在合并两个区间的时候才能发挥作用。

区间合并

为了方便讲解,以下定义 \(\uparrow(x,y)\) 为“当这个集合的最小不能表达数增加至 \(x\) 及以上时,这个集合的最小不能表达数将会增加 \(y\)”。

首先,有两个区间,它们的 mx 值分别是 \(x\)\(y\)。显然,新区间内的 mx 值不会小于 \(x+y\)

而这两个区间分别拥有自己的 \(\set{\uparrow(a_1,a_2)}\)\(\set{\uparrow(b_1,b_2)}\) 的信息。先将它们启发式合并至一个辅助集合中。

现在新区间内的 mx 值是 \(x+y\)。既然 mx 相对于 \(x\)\(y\) 都增加了,那么 mx 大概率会满足一些形如 \(\uparrow(c_1,c_2)\) 的性质。

这个时候,利用 multiset 自动升序排序的性质,我们可以快速地应用所有满足要求的性质。

但是,如果在这一步之后直接返回新区间的信息,就会被一组一个 \(1\) 都没有的数据卡掉。

所以我们仍然需要优化。

考虑两个形如 \(\uparrow(a_1,a_2)\)\(\uparrow(b_1,b_2)\) 的信息,保证 \(a_1<b_1\)。我们想象有一个最小不能表达数为 \(a_1\) 的集合 \(T\)。那么,\(\uparrow(a_1,a_2)\) 就会被应用到 \(T\) 上。那么在应用这个条件的时候,会不会同时满足 \(\uparrow(b_1,b_2)\)?实际上,如果 \(a_1+a_2\ge b_1\),那么这两个条件会先后被满足。于是,在满足上述条件的情况下,\(\uparrow(a_1,a_2)\)\(\uparrow(b_1,b_2)\) 可以被合并成 \(\uparrow(a_1,a_2+b_2)\)

上一段话描述的就是线段树节点结构体内 operator+ 对信息进行压缩的流程,可以结合阅读。

剩下的就是一般的线段树查询操作了。

经过一些口胡可以证出,线段树上的任何节点上挂载的 \(\set{\uparrow(a_1,a_2)}\) 集合满足 \(|{\set{\uparrow(a_1,a_2)}}|\le \log V\)。在此题中,\(V\le 10^9\),故可以通过。

时间复杂度 \(O(n\log n\log V)\)

代码

#include <iostream>
#include <set>
using namespace std;
const int N = 1e5 + 10;
using ll = long long;
int n, m, a[N], l, r;
using pll = pair<ll, ll>;
template <typename _Tp>
inline void read(_Tp &x)
{
    char ch;
    bool neg = false;
    while (ch = getchar(), !isdigit(ch))
        neg |= (ch == '-');
    x = ch - '0';
    while (ch = getchar(), isdigit(ch))
        x = x * 10 + ch - '0';
    x = (neg ? -x : x);
}
template <typename _Tp, typename... _Ano>
inline void read(_Tp &x, _Ano &...ano)
{
    read(x);
    read(ano...);
}
struct st
{
    ll mx;
    multiset<pll> prep;
    st operator+(const st &x) const
    {
        // fprintf(stderr, "call %lld %lld\n", mx, x.mx);
        // fflush(stderr);
        st res = {mx + x.mx, {}};
        multiset<pll> tst;
        if (prep.size() >= x.prep.size())
        {
            tst = prep;
            for (auto &i : x.prep)
                tst.insert(i);
        }
        else
        {
            tst = x.prep;
            for (auto &i : prep)
                tst.insert(i);
        }
        while (tst.size() and res.mx >= tst.begin()->first)
        {
            res.mx += tst.begin()->second;
            tst.erase(tst.begin());
        }
        if (!tst.size())
            return res;
        ll req = tst.begin()->first, sm = 0;
        for (auto &[trq, v] : tst)
        {
            if (req + sm >= trq)
            {
                sm += v;
                continue;
            }
            res.prep.insert({req, sm});
            req = trq, sm = v;
        }
        res.prep.insert({req, sm});
        return res;
    }
};
st tr[N << 2];
void build(int x, int l, int r)
{
    // fprintf(stderr, "%d %d %d\n", x, l, r);
    // fflush(stderr);
    if (l == r)
    {
        tr[x] = (a[l] == 1 ? st{1, {}} : st{0, {{a[l] - 1, a[l]}}});
        return;
    }
    int mid = (l + r) >> 1;
    build(x << 1, l, mid);
    build(x << 1 | 1, mid + 1, r);
    tr[x] = tr[x << 1] + tr[x << 1 | 1];
}
st query(int x, int l, int r, int lb, int rb)
{
    if (l >= lb and r <= rb)
        return tr[x];
    st res = st{0, {}};
    int mid = (l + r) >> 1;
    if (lb <= mid)
        res = res + query(x << 1, l, mid, lb, rb);
    if (rb > mid)
        res = res + query(x << 1 | 1, mid + 1, r, lb, rb);
    return res;
}
int main()
{
    read(n, m);
    for (int i = 1; i <= n; i++)
    {
        read(a[i]);
    }
    build(1, 1, n);
    for (int i = 1; i <= m; i++)
    {
        // fprintf(stderr, "%d\n", i);
        // fflush(stderr);
        read(l, r);
        printf("%lld\n", query(1, 1, n, l, r).mx + 1);
    }
}
posted @ 2024-07-12 16:30  丝羽绫华  阅读(1)  评论(0编辑  收藏  举报