题解: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

\[f_i=\max_{i-j\leq m}\{\operatorname{mex}(j+1, i)(sum_i-sum_j)+f_j\} \]

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:

\[B(sum_i-sum_j)+f_j=B\cdot sum_i+\max_{l\leq j\leq r}\{-sum_j\cdot B+f_j\} \]

考虑求出 \(\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;
}

posted @ 2023-11-06 14:38  caijianhong  阅读(35)  评论(0)    收藏  举报