题解 LGP10144【[WC/CTS2024] 水镜】

题解 P10144【[WC/CTS2024] 水镜】

题目描述

给定一个长度为 \(n\) 的正整数序列 \(h_1, h_2,\cdots , h_n\),求满足以下所有条件的
二元组 \((u, v)\) 的数量:

  • \(1 \le u < v \le n\),且 \(u, v\) 为整数;
  • 存在一个正实数 \(L\) 以及一个长度为 \((v - u + 1)\) 的序列 \(r_u, r_{u+1},\cdots , r_v\) 满足以下
    所有条件:
    • \(\forall u \le i \le v\),记 \(h'_i = 2L - h_i\),则 \(r_i \in \{h_i,h'_i\}\),特别地,当 \(h_i = h'_i\) 时,\(r_i = h_i\)
    • \(\forall u \le i < v, r_i < r_{i+1}\)
  • \(2\le n\le 5\times 10^5\)
  • \(\forall 1\le i\le n,1\le h_i\le 10^{12}\)

solution

\(p_i=\max(h_i, 2L - h_i)\),明显 \(p_i\geq L\),我们将 \(r_i\) 的选择分成 \(<L\) 的和 \(\geq L\) 的两段,\(<L\) 的段必然是 \(\min(h_i, 2L-h_i)\) 单调升,也就是 \(\max(h_i, 2L-h_i)\) 单调减;\(\geq L\) 的段是 \(p_i\) 单调升;所以最后刻画得到,区间 \([l, r]\) 合法,当且仅当存在 \(L,t\),使得 \(p_l>p_{l+1}>\cdots>p_t?p_{t+1}<p_{t+2}<\cdots<p_r\)。这里你理解一下当 \(L=h_t=h_{t+1}\) 的时候是个什么东西,你使得 \(L\) 向下移动 \(\epsilon\) 就行,因为原来 \(p_{t-1}>p_t\) 所以移动之后也会满足这个性质。所以 \(p_t?p_{t+1}\) 这个问号是什么都可以。

反着写一遍条件即为“区间 \([l, r]\) 中不存在 \(t\) 使得 \(p_{t-1}\leq p_t\geq p_{t+1}\)”(存在这个 \(t\) 就一定不合法,合法一定找不到 \(t\))。这个条件也比较严重,我们考虑 \(p_{i}\leq p_{i+1}\) 是什么时候成立的。

  • \(p_i=p_{i+1}\) 时你想象一下就是 \(L=(h_i+h_{i+1})/2\) 是合法的。
  • \(h_i=h_{i+1}\) 时,\(p_i=p_{i+1}\) 这个事实永恒不变。
  • \(h_i< h_{i+1}\) 时,\(L\) 的减少导致 \(p_i\) 减少,所以当 \(L\leq (h_i+h_{i+1})/2\)\(p_i\leq p_{i+1}\)
  • \(h_i>h_{i+1}\) 时,\(L\) 的减少导致 \(p_{i+1}\) 减少,所以当 \(L\geq (h_i+h_{i+1})/2\)\(p_i\leq p_{i+1}\)
  • (实现时把它当做是 \(2L\) 的限制去掉 \(/2\)

这导出一个很严重的做法,即考虑固定 \(l\),向右扫 \(r\),每次把 \([l+1,r-1]\) 这个区间的 \(i\) 对应的 \(L\) 不能选的区间并起来,如果并集不是全集,我们就能找到这样的 \(L\),它就是一个合法区间。

这里,这个区间的 \(i\) 对应的 \(L\) 不能选的区间,仔细推敲一下发现要么是空,要么是前后缀,要么是全集。大致证明是说,如果有一边的 \(h\) 相等马上结束;若连续三个 \(h\) 单调,则发现这段前缀和后缀交出来,因为单调,所以是空集(可以看着代码理解一下);否则是前缀之间的交或者后缀之间的交。有了这样的性质可以简化代码。

抛开以上这些东西不谈我们不难发现一个区间合法那么它的子区间合法,说明它可以双指针。

不删除双指针

(also known as "baka's trick")

定义一个问题可以双指针,即要找出所有 \(f(l, r)=1\) 的区间,\(f\) 满足 \(f(l, r)\in \{0, 1\}\) 且若 \(f(l, r)=1\)\(f(l_0, r_0)=1\ (l\leq l_0\leq r_0\leq r)\)。一般的做法是维护两个指针 \(l, r\),不断\(r\) 右移,将 \(l\) 右移,时刻维护 \(f(i\in[l, r], r)=1\land f(i-1, r)=0\)。这样的复杂度取决于 \(f\) 怎么算。

\(f(l, r)\) 依赖于某个 \(g(l, r)\) 的信息,即知道 \(g(l, r)\) 可以唯一确定 \(f(l, r)\),那么可以动态的维护 \(g(l, r)\)\(l, r\) 是上文的两个指针),算出 \(f\) 判断。注意这里的 \(g\) 的信息需要支持插入删除。

\(g\) 不支持删除,但是支持合并,同时信息量较少,我们就引出了不删除双指针。具体做法是额外维护一个 \(mid\in[l, r]\),以及 \(resl[i]=g(i, mid), resr=g(mid+1, r)\)。这样一来 \(g(l, r)=resl[l]+resr\),对于 \(l\) 的右移,在 \(l>mid\) 之前,都可以快速计算 \(g(l, r)\) 从而判断是否需要继续右移。当 \(l>mid\) 的时候,说明此时 \([mid, r]\) 是一个不合法的区间,我们的做法是将 \(mid\) 右推到 \(r\),放弃之前的 \(resl[], resr\) 把他们改成单位元,然后令 \(l=mid\)\(l\) 左移,直到 \([l, r]\) 不合法,在左移的过程中计算 \(resl[l]=g(l, l)+resl[l+1]\)。因为已知原来的 \([mid, r]\) 是不合法区间,所以新的 \(l\) 左移不会超过 \(mid\)。这样一来 \(mid, r\) 只会右移,\(l\) 只会在两个 \(mid\) 断点之间跑一回合,时间复杂度是 \(O(n)\) 次信息合并。

具体算法实现大致如下:

  static node rel[500010], rer;
  for (int r = 1, l, mid; r < n; ) {
    for (mid = l = r, rel[l + 1] = rer = E; l > 0; --l) {
      rel[l] = merge(I[l], rel[l + 1]);
      if (!rel[l].vaild()) break;
    }
    // [l, r] is invaild or l == 1
    for (++l; ans += r - l + 1, ++r <= n; ) {
      rer = merge(rer, I[r]);
      while (l <= mid && !merge(rel[l], rer).vaild()) ++l;
      if (l > mid) break;
    }
  }

code

#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
typedef long long LL;
constexpr LL inf = 1e18;
struct node {
  LL l, r;
  bool empty() { return l > r; }
};
node cap(node a, node b) {
  if (a.empty() || b.empty()) return node{inf, -inf};
  if (a.l > b.l) swap(a, b);
  else if (a.l == b.l && a.r > b.r) swap(a, b);
  debug("cap([%lld, %lld], [%lld, %lld]) = [%lld, %lld]\n", a.l, a.r, b.l, b.r, b.l, a.r);
  return node{b.l, a.r};
}
node merge(node a, node b) {
  return node{min(a.l, b.l), max(a.r, b.r)};
}
node append(node a, node b) {
  if (!b.empty()) {
    if (b.l == -inf) a.r = max(a.r, b.r);
    if (b.r == inf) a.l = min(a.l, b.l);
  }
  return a;
}
int n;
LL h[500010];
node I[500010], E = node{inf + 1, -inf - 1}; // E 鏄崟浣嶅厓
int main() {
#ifndef LOCAL
  cin.tie(nullptr)->sync_with_stdio(false);  
#endif
  cin >> n;
  for (int i = 1; i <= n; i++) cin >> h[i];
  for (int i = 2; i < n; i++) {
    node L = h[i - 1] < h[i] ? node{-inf, h[i - 1] + h[i]} : node{h[i - 1] + h[i], inf};
    if (h[i - 1] == h[i]) L = node{-inf, inf};
    node R = h[i] < h[i + 1] ? node{h[i] + h[i + 1], inf} : node{-inf, h[i] + h[i + 1]};
    if (h[i] == h[i + 1]) R = node{-inf, inf};
    I[i] = cap(L, R);
    debug("I[%d] = [%lld, %lld]\n", i, I[i].l, I[i].r);
    if (!I[i].empty()) assert(I[i].l == -inf || I[i].r == inf);
    I[i] = append(E, I[i]);
  }
  LL ans = n - 1;
  static node rel[500010], rer;
  for (int r = 2, l, mid; r < n; ) {
    for (mid = l = r, rel[l + 1] = rer = E; l > 1; --l) {
      rel[l] = merge(I[l], rel[l + 1]);
      if (!rel[l].empty()) break;
    }
    // [l, r] is invaild or l == 1
    for (++l; ans += r - l + 1, ++r < n; ) {
      rer = merge(rer, I[r]);
      while (l <= mid && !merge(rel[l], rer).empty()) ++l;
      if (l > mid) break;
    }
  }
  cout << ans << endl;
  return 0;
}

posted @ 2024-02-16 13:56  caijianhong  阅读(31)  评论(0编辑  收藏  举报