【模板】不删除双指针

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;
    }
  }

版本二

不删除的双指针,本质上就是要维护一个队列,需要支持 push_back()pop_front() 以及查询全局的某种信息的和,而信息只有结合律,不能作差。以下假设信息合并是 \(O(1)\) 的。

我们回忆,如果不是维护队列而是维护栈,那么我们可以时刻维护栈中所有元素到栈底的信息的和,\(O(1)\) 即可支持所有操作。

考虑用两个栈底相连的栈模拟队列。假设有两个栈 \(A, B\)\(A\) 的栈底连着 \(B\) 的栈底。push_back() 时,往 \(B\)push 元素,并维护 \(B\) 中元素的和。pop_front() 时,如果 \(A\) 栈为空,将 \(B\) 栈的所有元素全部逆序倒进 \(A\) 栈中,重新维护 \(A\) 中所有元素到栈底的信息的和,然后从 \(A\)pop 一个元素,就是将最前的一个元素 pop 掉了。求全局的信息,直接将 \(A\) 栈的信息和与 \(B\) 栈的信息和相加即为全局的答案。

于是我们就完成了这一个队列,剩余部分与普通双指针一样。

点击查看代码
template <class T>
struct qwqUwU {
  T a[410], b[410], suf;
  int topa, topb;
  void clear() {
    topa = topb = 0;
  }
  void push(const T& x) {
    if (topa) suf = suf + x, a[++topa] = x;
    else suf = a[++topa] = x;
  }
  void pop() {
    if (topb) return --topb, void();
    assert(topa);
    swap(topa, topb);
    b[1] = a[topb];
    for (int i = 2; i <= topb; i++) b[i] = a[topb - i + 1] + b[i - 1];
    --topb;
  }
  T sum() {
    if (!topa && !topb) return /*零*/;
    if (!topa) return b[topb];
    if (!topb) return suf;
    return b[topb] + suf;
  }
};
posted @ 2024-09-28 21:16  caijianhong  阅读(1)  评论(0编辑  收藏  举报