P9334 [JOISC 2023] 水羊羹 2 题解

Description

给定一个长度为 \(n\) 的序列,有 \(q\) 次单点修改,同时修改后区间询问把这个区间划分成若干区间和交替的子段的最大划分段数。

\(n\leq 2.5\times 10^5,q\leq 5\times 10^4\)

Solution

首先注意到那些作为小段的区间一定长度为 \(1\),否则把其左右的端点给两边的大段一定更优。

考虑对于小段 dp,就得到一个单次 \(O(n\log n)\) 的 dp 做法,但是这个东西完全无法优化。


考虑挖掘大段的性质。

容易发现一个大段 \([l,r]\) 合法,当且仅当 \(\text{sum}(l,r)>\max\{a_{l-1},a_{r+1}\}\)。类似 [JOISC2022] 鱼 2 的想法,对于一个 \(l\),最短的好段 \([l,r]\) 的长度为 \(O(\log V)\) 级别。因为如果 \([l,r]\) 不合法,则 \(\text{sum}(l,r)\leq a_{r+1}\),那么加入 \(a_{r+1}\) 后总和会翻倍,所以总和最多翻 \(O(\log V)\) 次倍就能得到一个好段。

但是根据上面的做法,大段要求之间只隔 \(1\) 个数,所以不一定是选最短的好段。

不过可以证明只要选择的大段满足 \(\text{sum}(l,r)>\max\{a_{l-1},a_{r+1}\}\) 且相邻的大段之间间隔至少 \(1\) 即可。证明如下:对于相邻的两个大段 \([l_1,r_1],[l_2,r_2]\),如果 \(a_{r_1+1}\geq a_{l_2-1}\),则让 \(r_1\leftarrow l_2-2\),否则让 \(l_2\leftarrow r_1+2\),这么调整一定合法且大段之间只相隔 \(1\) 了。

所以求出以 \(l\) 为左端点的最短好段 \([l,f_l]\) 后就可以贪心了,每次找到 \(l\geq p+2\) 且最小的 \(f_l\) 拓展即可。

由于 \(f_l-l\)\(O(\log V)\) 级别,所以每次只会跳 \(O(\log V)\),可以用线段树维护这个区间的前 \(\log V\) 的点跳出这个区间的第一个位置,每次 \(O(\log V)\) 合并。同时每次只会修改 \(O(\log V)\)\(f_l\),所以暴力修改即可。

时间复杂度:\(O(n\log V+m\log n\log^2V)\)

Code

#include <bits/stdc++.h>

// #define int int64_t

using i64 = int64_t;

const int kMaxN = 2.5e5 + 5;

int n, q, lim = 70;
int a[kMaxN], f[kMaxN], g[kMaxN], ff[kMaxN], gg[kMaxN];

struct Node {
  int l, r;
  std::vector<std::pair<int, int>> vec;
  friend Node operator +(Node &a, Node &b) {
    if (a.r - a.l + 1 <= 0) return b;
    if (b.r - b.l + 1 <= 0) return a;
    static Node ret;
    assert(a.r == b.l - 1);
    ret.l = a.l, ret.r = b.r, ret.vec.clear();
    for (int i = ret.l; i <= std::min(ret.l + lim, ret.r); ++i) {
      if (i <= a.r) {
        auto [p, c] = a.vec[i - a.l];
        if (g[p + 2] <= b.r)
          ret.vec.emplace_back(b.vec[g[p + 2] - b.l].first, c + 1 + b.vec[g[p + 2] - b.l].second);
        else
          ret.vec.emplace_back(p, c);
      } else {
        ret.vec.emplace_back(b.vec[i - b.l]);
      }
    }
    return ret;
  }
};

struct SGT {
  Node t[kMaxN * 4];
  void pushup(int x) {
    t[x] = t[x << 1] + t[x << 1 | 1];
  }
  void build(int x, int l, int r) {
    if (l == r) {
      t[x].l = t[x].r = l;
      t[x].vec = {{l, 0}};
      return;
    }
    int mid = (l + r) >> 1;
    build(x << 1, l, mid), build(x << 1 | 1, mid + 1, r);
    pushup(x);
  }
  void update(int x, int l, int r, int ql, int qr) {
    if (l > qr || r < ql || l == r) return;
    int mid = (l + r) >> 1;
    update(x << 1, l, mid, ql, qr), update(x << 1 | 1, mid + 1, r, ql, qr);
    pushup(x);
  }
  Node query(int x, int l, int r, int ql, int qr) {
    if (l > qr || r < ql) return {0, -1, {}};
    else if (l >= ql && r <= qr) return t[x];
    int mid = (l + r) >> 1;
    Node ls = query(x << 1, l, mid, ql, qr), rs = query(x << 1 | 1, mid + 1, r, ql, qr);
    return ls + rs;
  }
} sgt;

struct BIT {
  i64 c[kMaxN];
  void upd(int x, int v) {
    for (; x <= n; x += x & -x) c[x] += v;
  }
  i64 qry(int x) {
    i64 ret = 0;
    for (; x; x -= x & -x) ret += c[x];
    return ret;
  }
  i64 qry(int l, int r) { return qry(r) - qry(l - 1); }
} bit;

void prework() {
  g[n + 1] = g[n + 2] = gg[n + 1] = gg[n + 2] = n + 1;
  for (int i = n; i; --i) {
    i64 sum = 0;
    f[i] = ff[i] = n + 1;
    for (int j = i; j <= n; ++j) {
      sum += a[j];
      if (sum > a[j + 1] && ff[i] == n + 1) {
        ff[i] = j;
      }
      if (sum > a[i - 1] && sum > a[j + 1]) {
        f[i] = j; break;
      }
    }
    g[i] = std::min(g[i + 1], f[i]);
  }
  for (int i = 1; i <= n; ++i) {
    i64 sum = 0;
    gg[i] = 0;
    for (int j = i; j; --j) {
      sum += a[j];
      if (sum > a[j - 1]) {
        gg[i] = j; break;
      }
    }
  }
  sgt.build(1, 1, n);
}

void rebuild(int x) {
  for (int i = std::min(x + 1, n); i >= std::max(x - lim, 1); --i) {
    i64 sum = 0;
    f[i] = ff[i] = n + 1;
    for (int j = i; j <= n; ++j) {
      sum += a[j];
      if (sum > a[j + 1] && ff[i] == n + 1) {
        ff[i] = j;
      }
      if (sum > a[i - 1] && sum > a[j + 1]) {
        f[i] = j; break;
      }
    }
    g[i] = std::min(g[x + 2], f[i]);
    for (int j = i; j <= f[i] - 1; ++j)
      g[i] = std::min(g[i], f[j]);
  }
  sgt.update(1, 1, n, std::max(x - lim - 2, 1), std::min(x - 1, n - 2));
  for (int i = x; i <= std::min(x + lim, n); ++i) {
    i64 sum = 0;
    gg[i] = 0;
    for (int j = i; j; --j) {
      sum += a[j];
      if (sum > a[j - 1]) {
        gg[i] = j; break;
      }
    }
  }
}

int solve(int l, int r) {
  int ans1 = 1, ans2 = 0;
  if (bit.qry(l + 1, r) > a[l] || bit.qry(l, r - 1) > a[r]) ans2 = 2;
  if (ff[l] <= r) {
    int p = ff[l];
    ans1 = 1;
    auto pp = sgt.query(1, 1, n, p, r);
    p = pp.vec[0].first, ans1 += 2 * pp.vec[0].second;
    if (p < r) {
      ++ans1;
      if (gg[r] > p + 1) ++ans1;
    }
  }
  if (g[l + 1] <= r) {
    int p = g[l + 1];
    ans2 = 2;
    auto pp = sgt.query(1, 1, n, p, r);
    p = pp.vec[0].first, ans2 += 2 * pp.vec[0].second;
    if (p < r) {
      ++ans2;
      if (gg[r] > p + 1) ++ans2;
    }
  }
  return std::max(ans1, ans2);
}

void dickdreamer() {
  std::cin >> n;
  for (int i = 1; i <= n; ++i) {
    std::cin >> a[i];
    bit.upd(i, a[i]);
  }
  prework();
  std::cin >> q;
  for (int i = 1; i <= q; ++i) {
    int x, y, l, r;
    std::cin >> x >> y >> l >> r;
    bit.upd(x, y - a[x]);
    a[x] = y;
    rebuild(x);
    std::cout << solve(l + 1, r) << '\n';
  }
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}
posted @ 2025-02-14 16:29  下蛋爷  阅读(39)  评论(0)    收藏  举报