题解:SS231102D Calculate / QOJ7605 [PR #12] 划分序列 / Yet Another Mex Problem
题解 SS231102D【Calculate】
http://pjudge.ac/contest/1526/problem/21809
problem
计算哥给你一个长度为 \(n\) 的非负整数序列 \(a\)(\(0 \le a_i \le n\))与一个正整数 \(m\)(\(m \le n\)),请你求出将 \(a\) 划分成若干段,且每段的长度都 \(\le m\),能得到的最大价值。
定义 \(a\) 序列的一个子段 \(a[l, r]\) 的价值为 \(\text{MEX}(a[l, r]) \times \sum_{i = l}^r a_i\),定义一种划分方案的价值为划分出的每个子段的价值和,请你求出价值最大的划分方案的价值。
其中,对于一个非负整数序列 \(b\),\(\text{MEX}(b)\) 表示最小的未在 \(b\) 中出现过的非负整数,比如 \(\text{MEX}([1, 9, 1, 9, 8, 1, 0]) = 2\)。
\(1 \le m \le n \le 2 \times 10^5\)。
solution part 0
solution part 1(mex 分段)
从左到右扫,考虑维护所有 mex 的左端点连续段,\(a_j\) 所在的连续段的 mex 称为 \(B_j\)。\(B\) 明显的单调递减。考虑加进去一个数之后,每一个 mex 连续段会分裂,因为分裂了之后不再合并,所以这样分裂的总次数不超过 \(O(n)\)。
考虑快速的找出被分裂的区间,相当于是反复找出最小的 \(l'\) 使得 \(\operatorname{mex}(l'+1, r)=\operatorname{mex}(l+1, r)\),用单调栈之类的维护连续段。考虑可持久化线段树,版本 \(r\) 是一棵关于颜色的线段树,第 \(i\) 个位置是颜色 \(i\)(颜色就是 \(a\) 的值)的最后一次出现时间,不妨记为 \(last_i\),如果满足 \(\forall 0\leq j\leq c, l<last_j\),则说 \(\operatorname{mex}(l+1, r)\) 至少为 \(c+1\)。考虑直接在这棵线段树上二分,则可以直接 \(O(\log n)\) 的时间内找到 \(l'\),也就能用 \(O(n\log n)\) 的时间找出所有连续段。
2024 年 PR #12 又是这个题,回看了一下发现下面贴的代码是假的,等会补。注意,这里是分裂连续段。另外根本不需要维护具体的连续段。
solution part 2(转化)
考虑已经知道了 \([l,r]\) 这一段的 \(\forall l\leq j\leq r, \operatorname{mex}(j+1, i)=B\)。那么考虑原来那个 DP:
考虑求出 \(\max_{l\leq j\leq r}\{-sum_j\cdot B+f_j\}\),令 \(F_j(x)=-sum_jx+f_j\),则就是 \(\max_{l\leq j\leq r}F_j(B)\),因为 \(B\) 是常数,所以如果我们有一个数据结构支持单点插入 \(F(x)\),区间查询 \(\max_{l\leq i\leq r}F_i(x)\),我们就能求出这个东西,记为 \(C\),我们先把他当做黑盒。
然后将 \(B\cdot sum_i+\max_{l\leq j\leq r}\{-sum_j\cdot B+f_j\}\) 看作 \(B\cdot sum_i+C\),那么现在 \(B, C\) 是两个常数,看作是关于 \(sum_i\) 的一次函数,进行一波区间插入区间最大值,但是想想发现不用,因为 mex 单调不降,所以不需要删除;然后每次查询的总是一段滑动窗口,有一个创举是将这个一次函数 \(H(x)=Bx+C(l\leq x\leq r)\) 插入到 \(l\) 上,修改变成单点,区间查询时顺带查询左端点没有被包含的一个小部分,随意二分一下是哪个块。于是只需要实现黑盒子并进行 \(O(n)\) 次调用黑盒子即可。
solution part 3(黑盒)
如果我们有一个数据结构支持单点插入 \(F(x)\),区间查询 \(\max_{l\leq i\leq r}F_i(x)\),我们就能求出这个东西。
考虑如何做。考虑树套树,外层是一棵普通下标线段树,内层是李超线段树,每一次单点修改就在外层线段树上爬,插入 \(O(\log n)\) 棵李超线段树。区间查询就是找到 \(O(\log n)\) 个李超线段树,在上面查询一下。单次复杂度是 \(O(\log^2 n)\)。
更多的复杂度优化
考虑到 mex 和黑盒部分是割裂的,可以先求出所有 mex 连续段。然后考虑扫描左端点,扫到 \(l\),处理左端点为 \(l\) 的 mex 连续段的那些函数,插入一棵李超线段树上,把区间变成全局。由于我是听 Mobius127 说的,我也不清楚具体怎么做。
支持删除的黑盒子
另一个神秘的 gym 题,觉得很有趣:
- 一个序列 \(\{f_i(x)\}\) 维护 \(n\) 个一次函数。
- 单点修改一次函数。
- 区间查询 \(\max_{l\leq i\leq r}f_i(x_0)\)。
- 要求 \(O(n\log ^2n)\)。
这题有三个维度:时间,下标,坐标轴。因为凸包相关的东西不适合删除,第一步是线段树分治,把一个函数的出现时间段拆成 \(O(\log n)\) 个时间线段树区间挂上,一个询问挂到询问时间对应的叶子往上爬的所有祖先。在一个线段树节点上,就不需要管时间了。
忽略时间后,变成静态问题。还要树套树,下标线段树的每一个节点维护一个凸包,先静态的建出树,逐个 maintain 上去,归并求凸包(其实不是归并,就把所有涉及点拿出来再算很多次再求凸包之类的,没有细想)。对于询问有暴力的想法是找出 \(O(\log n)\) 个下标区间,在凸包上二分,于是这是 \(O(n\log ^3 n)\) 的。
然而可以不进行二分。将所有询问对应拆到下标区间上,在下标线段树节点上对询问按照 \(x\) 排序,然后直接扫一遍,就能干掉一个 \(O(\log n)\)。
于是总的复杂度是 \(O(n\log ^ 2n)\),因为我是口胡的,所以没有代码。
code
solution 3(5s 200M)
#include <algorithm>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <functional>
#include <tuple>
#include <vector>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
typedef long long LL;
template <int N>
struct sebtree {
int ch[N << 5][2], minv[N << 5], tot;
sebtree() : tot(-1) { newnode(0); }
int newnode(int q) {
int p = ++tot;
if (!q)
ch[p][0] = ch[p][1] = minv[p] = 0;
else
memcpy(ch[p], ch[q], sizeof ch[p]), minv[p] = minv[q];
return p;
}
void maintain(int p) { minv[p] = min(minv[ch[p][0]], minv[ch[p][1]]); }
int modify(int x, int k, int p, int l, int r) {
int q = newnode(p);
if (l == r) return minv[q] = k, q;
int mid = (l + r) >> 1;
if (x <= mid)
ch[q][0] = modify(x, k, ch[p][0], l, mid);
else
ch[q][1] = modify(x, k, ch[p][1], mid + 1, r);
maintain(q);
return q;
}
pair<int, int> binary(int L, int p, int l, int r) {
if (l == r) return {minv[p], l};
int mid = (l + r) >> 1;
if (minv[ch[p][0]] < L)
return binary(L, ch[p][0], l, mid);
else
return binary(L, ch[p][1], mid + 1, r);
}
};
int n;
LL s[200010];
template <class T, int id>
struct func {};
template <class T>
struct func<T, 1> {
T k, b;
func() : func(0, -1e18) {}
func(LL k, LL b) : k(k), b(b) {}
T operator()(int x) const { return k * x + b; }
};
template <class T>
struct func<T, 2> {
T k, b;
func() : func(0, -1e18) {}
func(LL k, LL b) : k(k), b(b) {}
T operator()(int x) const { return k * s[x] + b; }
};
template <int id>
struct lichaotree {
private:
static constexpr int HJHAKIOI = 200010 * 64;
static int ch[HJHAKIOI][2], tid[HJHAKIOI], tot;
static int newnode(int fid) {
int p = ++tot;
ch[p][0] = ch[p][1] = 0;
tid[p] = fid;
return p;
}
int root;
void _insert(int fid, int &p, int l, int r) {
if (!p) p = newnode(fid);
int mid = (l + r) >> 1;
switch ((tgs[fid](l) <= tgs[tid[p]](l)) + (tgs[fid](r) <= tgs[tid[p]](r))) {
case 0:
tid[p] = fid;
break;
case 1:
_insert(fid, ch[p][0], l, mid);
_insert(fid, ch[p][1], mid + 1, r);
break;
}
}
LL _query(int x, int p, int l, int r) {
if (!p) return -1e18;
int mid = (l + r) >> 1;
if (x <= mid)
return max(tgs[tid[p]](x), _query(x, ch[p][0], l, mid));
else
return max(tgs[tid[p]](x), _query(x, ch[p][1], mid + 1, r));
}
public:
static func<LL, id> tgs[3000010];
lichaotree() : root(0) {}
void insert(int fid) { _insert(fid, root, 0, n + 1); }
LL query(int x) { return _query(x, root, 0, n + 1); }
};
template <int id>
int lichaotree<id>::ch[lichaotree<id>::HJHAKIOI][2];
template <int id>
int lichaotree<id>::tot;
template <int id>
int lichaotree<id>::tid[lichaotree<id>::HJHAKIOI];
template <int id>
func<LL, id> lichaotree<id>::tgs[3000010];
template <int N, int id>
struct zkwtree {
lichaotree<id> t[N << 2];
int tot;
void insert(int p, const func<LL, id> &f) {
debug("id = %d, insert(%d, y = %lldx + %lld)\n", id, p, f.k, f.b);
lichaotree<id>::tgs[++tot] = f;
++p;
for (p += N; p; p >>= 1) t[p].insert(tot);
}
LL query(int L, int R, int x) {
debug("id = %d, query(%d, %d, %lld) = ", id, L, R, x);
LL res = static_cast<LL>(-1e18);
++L, ++R;
for (L += N - 1, R += N + 1; L ^ R ^ 1; L >>= 1, R >>= 1) {
if (~L & 1) res = max(res, t[L ^ 1].query(x));
if (R & 1) res = max(res, t[R ^ 1].query(x));
}
debug("%lld\n", res);
return res;
}
};
int a[200010], m;
sebtree<200010> T;
int root[200010];
zkwtree<200010, 1> T1;
zkwtree<200010, 2> T2;
LL f[200010];
LL solve() {
static pair<int, int> stk[200010];
static int last[200010];
int top = 0;
auto updatestk = [&](int i) {
while (stk[top].first > last[a[i]]) --top;
vector<pair<int, int>> tmp;
int Rpos = i;
while (Rpos > stk[top].first) {
auto res = T.binary(Rpos, root[i], 0, n + 1);
tmp.emplace_back(Rpos, res.second);
Rpos = res.first;
}
reverse(tmp.begin(), tmp.end());
for (auto elem : tmp) {
int pos, col;
tie(pos, col) = elem;
T2.insert(stk[top].first,
func<LL, 2>(col, T1.query(stk[top].first, pos - 1, col)));
// if (stk[top].first < i - m && i - m <= pos - 1)
// f[i] = func<LL, 2>(col, T1.query(i - m, pos - 1, col))(/*s[i]*/
// i);
stk[++top] = elem;
}
last[a[i]] = i;
};
T1.insert(0, func<LL, 1>(0, 0));
for (int i = 1; i <= n; i++) {
updatestk(i);
for (int j = 1; j <= top; j++)
debug("(%d, %d), ", stk[j].first, stk[j].second);
debug("\n");
int spos = lower_bound(stk + 1, stk + top + 1,
make_pair(i - m + 1, static_cast<int>(-1e9))) -
stk;
if (stk[spos - 1].first < i - m && i - m <= stk[spos].first - 1)
f[i] = func<LL, 2>(stk[spos].second, T1.query(i - m, stk[spos].first - 1,
stk[spos].second))(i);
f[i] = max(f[i], T2.query(max(i - m, 0), i - 1, /*s[i]*/ i));
T1.insert(i, func<LL, 1>(-s[i], f[i]));
debug("f[%d] = %lld\n", i, f[i]);
}
return f[n];
}
int main() {
#ifndef NF
freopen("calculate.in", "r", stdin);
freopen("calculate.out", "w", stdout);
#endif
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
s[i] = s[i - 1] + a[i];
}
s[n + 1] = s[n];
for (int i = 1; i <= n; i++)
root[i] = T.modify(a[i], i, root[i - 1], 0, n + 1);
printf("%lld\n", solve());
return 0;
}
本文来自博客园,作者:caijianhong,转载请注明原文链接:https://www.cnblogs.com/caijianhong/p/solution-SS231102D.html
浙公网安备 33010602011771号