WC2024 水镜 & baka's trick 记录

Statement

定义一个序列 \(h = (h_1, h_2, ..., h_n)\) 是优美的,当且仅当存在某个实数 \(L\),满足 \(h'_i \in \{h_i, L - h_i\}\),且 \(h_i' < h'_{i + 1}\)

现在给出一个 \(n\) 个数的序列 \(h_1, h_2, ..., h_n\),问存在多少对 \((l, r)\) 满足 \(l < r\)\(h_{l}, h_{l + 1}, ..., h_r\) 是优美的。

\(n \le 5\times10^5, h_i \le 10^{12}\)

Solution

不难发现对于一个序列满足条件的 \(L\) 是一个区间,然后设 \(f_{l, r, 0/1}\)\(h'_r = h_r/L-h_r\) 时的区间。

那么 \(f_{l, r - 1, 0} \cap (h_{r - 1} + h_r, +\infty) \to f_{l, r, 1}\)\(f_{l, r - 1, 1} \cap (- \infty , h_{r - 1} + h_r) \to f_{l, r, 0}\)。然后 \(h_i < h_{i + 1}\) 的话也可以 \(f_{l, r - 1, 0} \to f_{l, r, 0}\)\(h_i > h_{i + 1}\) 同理。其中 \(\to\) 代表取并集。

然后注意到这些东西有分配率,于是可以设计矩阵来转移,其中矩阵乘法是 \((\cup, \cap)\) 的。使用线段树可以快速求出 \([l, r]\) 区间是否合法,然后线段树二分可以 1log,但是太慢了。我们又注意到这个决策是单调的,可以使用双指针来维护,但是我们难以维护删除。

于是这里我们可以使用 Baka's trick,其实就是不删除双指针,它的使用条件是要求信息可快速合并。设 \(r\) 是当前扫描到的右端点,\(l\) 是当前最小的合法左端点。同时,我们再维护一个中间点 \(mid\),所有的 \(val(i, mid), l \le i \le mid\)\(val(mid + 1, r)\)

考虑 \(r \to r + 1\) 如何更新,首先更新 \(val(mid + 1, r)\)。然后把 \(l\) 向后扫,如果 \(l \le mid\) 就已经合法了,那我们不管。如果 \(l > mid\) 了,那么我们令 \(l \gets r, mid \gets r\)。然后让 \(l\) 向前扫直到不合法,并且维护出所有经过的 \(val(l, mid)\)。这样由于每个点至多前后各遍历一次,所以时间复杂度是 \(O(nM)\) 的,其中 \(M\) 为合并信息的时间复杂度。

在本题中就做到 \(O(2^3n)\) 了。比较好写,并且常数也不大。最慢的点没有 100ms。

qwq
#include<bits/stdc++.h>
#define ll long long
#define pb emplace_back
#define pir pair<int, ll>
#define fi first
#define se second
#define inv(x) qpow(x, mod - 2)
#define il inline
#define mkpir make_pair
#define ull unsigned long long
#define umap unordered_map
using namespace std;

const int N = 5e5 + 10, M = 2e5 + 10;
const ll mod = 998244353, INF = 1e16;

/*
struct edge{
  int v, next;
}edges[M << 1];
int head[N], idx;

void add_edge(int u, int v){
  edges[++idx] = {v, head[u]};
  head[u] = idx;
}
*/

il ll qpow(ll x, ll y){
  ll ret = 1;
  for(; y; y >>= 1, x = x * x % mod) if(y & 1) ret = ret * x % mod;
  return ret;
}
il void chkmin(ll& x, ll y){if(y < x) x = y;}
il void chkmax(ll& x, ll y){if(y > x) x = y;}
il void chkmin(int& x, int y){if(y < x) x = y;}
il void chkmax(int& x, int y){if(y > x) x = y;}
il void chkmod(ll& x){x = (x + mod) % mod;}
il void ADD(ll& x, ll y){x += y; (x >= mod) ? (x -= mod) : 0;}
il void MUL(ll& x, ll y){x = x * y % mod;}
il ll min(ll x, ll y){return (x < y) ? x : y;}
il ll max(ll x, ll y){return (x > y) ? x : y;}
//#define int long long

struct Seg{
  ll l, r;
  il bool empty(){return (l >= r);}
};
il Seg operator &(struct Seg s1, struct Seg s2){return (Seg){max(s1.l, s2.l), min(s1.r, s2.r)};}
il Seg operator |(struct Seg s1, struct Seg s2){
  if(s1.empty()) return s2;
  if(s2.empty()) return s1;
  return (Seg){min(s1.l, s2.l), max(s1.r, s2.r)};
}
il void operator |=(struct Seg &s1, struct Seg s2){s1 = (s1 | s2);}
il void operator &=(struct Seg &s1, struct Seg s2){s1 = (s1 & s2);}

struct node{
  Seg S[2][2];
  il node(){
    for(int i = 0; i < 2; i++) for(int j = 0; j < 2; j++) S[i][j] = (Seg){-INF, INF};
  };
  il bool empty(){
    for(int i = 0; i < 2; i++) for(int j = 0; j < 2; j++) if(!S[i][j].empty()) return 0;
    return 1;
  }
}a[N], pre[N], em;

il node merge(struct node n1, struct node n2){
  node ret;
  for(int i = 0; i < 2; i++) for(int j = 0; j < 2; j++) ret.S[i][j] = (Seg){INF, -INF};
  for(int i = 0; i < 2; i++)
    for(int j = 0; j < 2; j++)
      for(int k = 0; k < 2; k++)
        ret.S[i][j] |= (n1.S[i][k] & n2.S[k][j]);
  return ret;
}

int n;
ll h[N];

signed main(){
  ios::sync_with_stdio(0);
  cin.tie(0); cout.tie(0);
  cin >> n; ll ans = 0; em.S[0][1] = em.S[1][0] = (Seg){INF, -INF};
  for(int i = 1; i <= n; i++) cin >> h[i];
  for(int i = 2; i <= n; i++){
    if(h[i - 1] < h[i]) a[i].S[0][0] = (Seg){-INF, INF};
    else a[i].S[0][0] = (Seg){INF, -INF};
    a[i].S[0][1] = (Seg){h[i - 1] + h[i], INF};
    a[i].S[1][0] = (Seg){-INF, h[i - 1] + h[i]};
    if(h[i - 1] > h[i]) a[i].S[1][1] = (Seg){-INF, INF};
    else a[i].S[1][1] = (Seg){INF, -INF};
  }
  node now; 
  int l = 1, mid = 0;
  for(int r = 1; r <= n; r++){
    now = merge(now, a[r]);
    while(l <= mid){
      if(!merge(pre[l], now).empty()) break;
      l++;
    }
    if(l > mid){
      l = mid = r; pre[l] = now = em;
      l--;
      for(; l; l--){
        pre[l] = merge(a[l + 1], pre[l + 1]);
        if(pre[l].empty()) break;
      } l++;
    } ans += r - l;
  } cout << ans;

  return 0;
}

posted @ 2025-09-08 21:31  Little_corn  阅读(20)  评论(0)    收藏  举报