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

浙公网安备 33010602011771号