NowCoder 7605C Solution
题面
思路
考虑这个问题。
现有一个最小不能表示数为 \(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);
}
}