学习笔记:分治处理区间最值相关的计数 / DP

· · 算法·理论

总述

有如下两类与区间最值 / 区间最值位置相关的问题。

第一类:
统计满足一定条件的区间个数 / 区间权值和。
例如 \sum_{l=1}^{n}\sum_{r=l}^n (\max_{i=l}^ra_i \times \min_{j=l}^r a_j)

第二类:
有最值条件约束 / 最值贡献的动态规划。
例如 f_i=\max_{j=1}^{i-1}(f_j+\max_{k=j+1}^ia_k)

此时可以考虑分治计算。

记当前处理的区间为 [l,r]
取区间中点 mid=\lfloor \frac{l+r}{2} \rfloor
对于端点都在 [l,mid](mid,r] 的区间,可以递归计算。
仅处理跨区间的答案即可。

由于跨区间,显然满足:区间一定包含中点 midmid+1
那么可以对左区间求出后缀最值,对右区间求出前缀最值。
这样 \max_{i=l}^r a_i 就可以直接 O(1) 计算。

由于是前缀 / 后缀最值,所以最值还具有单调性。
此时就可以方便地计算贡献。

也可以拓展到最值位置,并且最值位置也有单调性。

CF1156E Special Segments of Permutation

Luogu Link。

Description

给定长度为 n 的排列 a_1,a_2,\dots,a_n
\sum_{l=1}^n \sum_{r=l}^n [a_l+a_r=\max_{i=l}^r a_i]

Solution

直接分治统计。

首先由于 a_i \in [1,n],区间 [i,i] 肯定不满足条件,可以不统计。

那么需要统计的就是 $a_x+a_y=\max(mx_x,mx_y)$ 的个数,其中 $x \in [l,mid],r \in (mid,r]$。 可以直接枚举右端点 $i$。 由于 $mx_i$ 在左右区间分别具有单调性,可以双指针找到最靠右的 $j \in [l,mid]$,使得 $mx_j \ge mx_i$。 那么 $\forall x \in [l,j]$,$\max(mx_x,mx_i)=mx_x$。 需要统计 $a_x+a_i=mx_x$ 的个数。 变形得到 $mx_x-a_x=a_i$。 开一个桶 $b1_v$ 记录 $[l,j]$ 内 $mx_x-a_x=v$ 的 $x$ 的个数。 每一个右端点直接将答案加上 $b1_{a_i}$ 即可。 $\forall x \in (j,mid]$,$\max(mx_x,mx_i)=mx_i$。 需要统计 $a_x+a_i=mx_i$ 的个数。 变形得到 $mx_i-a_i=a_x$。 开一个桶 $b2_v$ 记录 $(j,mid]$ 内 $a_x=v$ 的 $x$ 的个数。 每一个右端点直接将答案加上 $b2_{mx_i-a_i}$ 即可。 时间 $O(n \log n)$,空间 $O(n)$。 ### Code :::info[Code] ```cpp #include<bits/stdc++.h> using namespace std; #define ll long long #define rep(a,b,c) for(int a=(b);a<=(c);++a) #define per(a,b,c) for(int a=(b);a>=(c);--a) const int N = 2e5 + 5; int n; int a[N]; int mx[N]; ll ans; int b1[N], b2[N]; void solve(int l, int r){ if (l >= r) return; int mid = (l + r) >> 1; solve(l, mid); solve(mid + 1, r); mx[mid] = a[mid], mx[mid + 1] = a[mid + 1]; per(i, mid - 1, l) mx[i] = max(mx[i + 1], a[i]); rep(i, mid + 2, r) mx[i] = max(mx[i - 1], a[i]); int j = mid; rep(i, l, mid) b1[mx[i] - a[i]]++; rep(i, mid + 1, r){ while (j >= l && mx[j] < mx[i])--b1[mx[j] - a[j]], ++b2[a[j]], --j; ans += b1[a[i]] + b2[mx[i] - a[i]]; } rep(i, l, mid) b1[mx[i] - a[i]] = b2[a[i]] = 0; } int main(){ ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); cin >> n; rep(i, 1, n) cin >> a[i]; solve(1, n); cout << ans << '\n'; return 0; } ``` ::: ## AT_abc282_h [ABC282Ex] Min + Sum [Luogu Link](https://www.luogu.com.cn/problem/AT_abc282_h)。 ### Description 给定长度为 $n$ 的两个序列 $A,B$ 和一个常数 $S$。 求 $\sum_{l=1}^n \sum_{r=l}^n [\min_{i=l}^r A_i+\sum_{i=l}^r B_i \le S]$。 ### Solution $\sum_{i=l}^r B_i$ 可以前缀和处理。 记 $s_i=\sum_{j=1}^i B_j$,那么 $\sum_{i=l}^r B_i=s_r-s_{l-1}$。 里面变成 $\min_{i=l}^r A_i+s_r-s_{l-1} \le S$。 同样从中点断开,定义左区间的 $mn_i$ 为 $A_i$ 的后缀最小值,右区间的 $mn_i$ 为 $A_i$ 的前缀最小值。 那么需要统计 $\min(mn_x,mn_y)+s_y-s_{x-1} \le S$ 的 $(x,y)$ 对数。 枚举每一个 $y \in (mid,r]$,双指针找到最靠右的 $j \in [l,mid]$,使得 $mn_j \le mn_i$。 那么 $\forall x \in [l,j]$,$\min(mn_x,mn_i)=mn_x$。 需要统计 $mn_x+s_i-s_{x-1} \le S$ 的个数。 变形得到 $mn_x-s_{x-1} \le S-s_i$。 把 $mn_x-s_{x-1}$ 和 $S-s_i$ 都离散化,使用 BIT 维护 $[l,j]$ 内的 $mn_x-s_{x-1}$ 权值数量。 对于每一个右端点 $i$,将答案加上 $(-\infty,S-s_i]$ 的和即可。 $\forall x \in (j,mid]$,$\min(mn_x,mn_i)=mn_i$。 需要统计 $mn_i+s_i-s_{x-1} \le S$ 的个数。 变形得到 $mn_i+s_i-S \le s_{x-1}$。 把 $mn_i+s_i-S$ 和 $s_{x-1}$ 都离散化,使用 BIT 维护 $(j,mid]$ 内的 $s_{x-1}$ 权值数量。 对于每一个右端点 $i$,将答案加上 $[mn_i+s_i-S,+\infty)$ 的和即可。 注意还有 $l=r$ 的情况在分治过程中不会被统计,特判即可。 时间 $O(n\log^2 n)$,空间 $O(n)$。 ### Code :::info[Code] ```cpp #include<bits/stdc++.h> using namespace std; #define ll long long #define rep(a,b,c) for(int a=(b);a<=(c);++a) #define per(a,b,c) for(int a=(b);a>=(c);--a) const int N = 2e5 + 5, V = 4e5 + 5; int n; ll S, a[N]; int b[N]; ll s[N], mn[N], ans; ll val[V]; int p1[N], p2[N]; int tot; void ins(ll x){val[++tot] = x;} int dc(ll x){return lower_bound(val + 1, val + tot + 1, x) - val;} struct BIT { int t[V]; void add(int p, int x){while (p <= tot) t[p] += x, p += p &-p;} int sum(int p){int res = 0; while (p) res += t[p], p &= p - 1; return res;} } t1,t2; void solve(int l, int r){ if (l == r) return void(ans += a[l] + b[l] <= S); int mid = (l + r) >> 1; solve(l, mid); solve(mid + 1, r); mn[mid] = a[mid], mn[mid + 1] = a[mid + 1]; per(i, mid - 1, l) mn[i] = min(mn[i + 1], a[i]); rep(i, mid + 2, r) mn[i] = min(mn[i - 1], a[i]); tot = 0; rep(i, l, mid) ins(mn[i] - s[i - 1]), ins(s[i - 1]); rep(i, mid + 1, r) ins(S - s[i]), ins(mn[i] + s[i] - S); sort(val + 1, val + tot + 1); tot = unique(val + 1, val + tot + 1) - (val + 1); rep(i, l, mid) p1[i] = dc(mn[i] - s[i - 1]), p2[i] = dc(s[i - 1]); rep(i, mid + 1, r) p1[i] = dc(S - s[i]), p2[i] = dc(mn[i] + s[i] - S); int j = mid; rep(i, l, mid) t1.add(p1[i], 1); rep(i, mid + 1, r){ while (j >= l && mn[j] > mn[i]) t1.add(p1[j],-1), t2.add(p2[j], 1), --j; ans += t1.sum(p1[i]) + mid - j - t2.sum(p2[i] - 1); } rep(i, l, j) t1.add(p1[i],-1); rep(i, j + 1, mid) t2.add(p2[i],-1); } int main(){ ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); cin >> n >> S; rep(i, 1, n) cin >> a[i]; rep(i, 1, n) cin >> b[i]; rep(i, 1, n) s[i] = s[i - 1] + b[i]; solve(1, n); cout << ans << '\n'; return 0; } ``` ::: ## P10162 [DTCPC 2024] 序列 [Luogu Link](https://www.luogu.com.cn/problem/P10162)。 ### Description 给定一个长度为 $n$ 的序列 $a_1,a_2,\dots,a_n$。 求 $\sum_{l-1}^{n-1}\sum_{r=l+1}^n\max(a_l-a_{l+1},a_{l+1}-\max(a_l,a_{l+2}),\dots,a_i-\max(a_{i-1},a_{i+1}),\dots,a_r-a_{r-1}) \bmod 2^{32}$。 ### Solution 发现在枚举左右端点的过程中,只有左右端点的值是特殊的,中间的全都是定值。 记 $val_i=a_i-\max(a_{i-1},a_{i+1})$。 那么最内层可以化为 $\max(a_l-a_{l+1},\max_{i=l+1}^{r-1} val_i,a_r-a_{r-1})$。 从中点断开,化为 $\max(a_l-a_{l+1},\max_{i=l+1}^{mid} val_i,\max_{i=mid+1}^{r-1} val_i,a_r-a_{r-1})$。 那么左端点贡献的是 $\max(a_l-a_{l+1},\max_{i=l+1}^{mid} val_i)$,右端点贡献的是 $\max(\max_{i=mid+1}^{r-1} val_i,a_r-a_{r-1})$。 记录 $f_i$ 为位置 $i$ 的贡献。 $\forall i \in [l,mid],f_i=\max(a_i-a_{i+1},\max_{j=i+1}^{mid} val_j)$,$\forall i \in (mid,r],f_i=\max(\max_{j=mid+1}^{i-1} val_j,a_i-a_{i-1})$。 枚举两个端点 $L \in [l,mid],R \in (mid,R]$,那么 $[L,R]$ 对答案的贡献就是 $\max(f_L,f_R)$。 拆开 $f_L$ 和 $f_R$ 的贡献。 $\forall L \in [l,mid]$,$f_L$ 有贡献当且仅当 $f_L \ge f_R$。 那么贡献就是 $f_L\sum_{R=mid+1}^r[f_R \le f_L]$。 同理,$\forall R \in (mid,r]$,$f_R$ 有贡献当且仅当 $f_R > f_L$。 那么贡献就是 $f_R\sum_{L=l}^{mid}[f_R > f_L]$。 将两种贡献分开计算,先将左右区间的 $f_i$ 分别排序,双指针即可求出 $\sum_{R=mid+1}^r[f_R \le f_L]$ 和 $\sum_{L=l}^{mid}[f_R > f_L]

时间 O(n \log^2 n),空间 O(n)

Code

:::info[Code]

#include<bits/stdc++.h>
using namespace std;
#define ui unsigned int
#define rep(a,b,c) for(int a=(b);a<=(c);++a)
#define per(a,b,c) for(int a=(b);a>=(c);--a)
const int N = 1e6 + 5;
int n;
int a[N], val[N], b[N];
ui ans;
void solve(int l, int r){
    if (l >= r) return;
    int mid = (l + r) >> 1;
    solve(l, mid);
    solve(mid + 1, r);
    int mx =-1145141919;
    per(i, mid, l) b[i] = max(mx, a[i] - a[i + 1]), mx = max(mx, val[i]);
    mx =-1145141919;
    rep(i, mid + 1, r) b[i] = max(mx, a[i] - a[i - 1]), mx = max(mx, val[i]);
    sort(b + l, b + mid + 1);
    sort(b + mid + 1, b + r + 1);
    int j = l;
    rep(i, mid + 1, r){
        while (j <= mid && b[j] < b[i])++j;
        ans += (ui)b[i] * (j - l);
    }
    j = mid + 1;
    rep(i, l, mid){
        while (j <= r && b[j] <= b[i])++j;
        ans += (ui)b[i] * (j - mid - 1);
    }
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> n;
    rep(i, 1, n) cin >> a[i];
    rep(i, 1, n) val[i] = a[i] - max(a[i + 1], a[i - 1]);
    solve(1, n);
    cout << ans << '\n';
    return 0;
}

:::

CF1849E Max to the Right of Min

Luogu Link。

Description

给定长度为 n 的序列 a_1,a_2,\dots,a_n。且保证 a_i 互不相同。
maxpos_{l,r} 为区间 [l,r] 最大 a_i 的下标,minpos_{l,r} 为区间 [l,r] 最小 a_i 的下标。
\sum_{l=1}^n\sum_{r=l}^n[maxpos_{l,r}>minpos_{l,r}]

Solution

首先对于 l=r 的情况,有 maxpos_{l,r}=minpos_{l,r}=l=r,可以不统计。

分治统计 l<r 的情况。

从中点拆开最值位置,变成左区间的后缀最值位置和右区间的前缀最值位置。

mxp_i 为前 / 后缀最大值位置,mnp_i 为前 / 后缀最小值位置。
显然可以 O(len) 递推求出来。

那么再额外维护两个指针 j1,j2,使得 [l,j1] 是使得 a_{mxp_j}>a_{mxp_i} 的极长前缀,[l,j2] 是使得 a_{mnp_j}<a_{mnp_i} 的极长前缀。

枚举右端点 x,然后分讨最值的情况。

  1. 两个都在左区间。此时 x \in [l,\min(j1,j2)],需要统计的是 \sum_{ x \in [1,\min(j1,j2)]}[mxp_x>mnp_x],额外预处理左区间 [mxp_x>mnp_x] 的前缀和即可。
  2. 两个都在右区间。此时 x \in (\max(j1,j2),mid],需要统计的是 \sum_{x \in (\max(j1,j2),mid]}[mxp_i>mnp_i]=(mid-\max(j1,j2))[mxp_i>mnp_i]
  3. 最大值在左区间,最小值在右区间。显然不满足条件。
  4. 最大值在右区间,最小值在左区间。条件显然成立。此时 x \in (j1,j2],有 \max(j2-j1,0)x 合法。

时间 O(n \log n),空间 O(n)

Code

:::info[Code]

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rep(a,b,c) for(int a=(b);a<=(c);++a)
#define per(a,b,c) for(int a=(b);a>=(c);--a)
const int N = 1e6 + 5;
int n;
int a[N];
int mxp[N], mnp[N];
int sum[N];
ll ans;
void solve(int l, int r){
    if (l >= r) return;
    int mid = (l + r) >> 1;
    solve(l, mid);
    solve(mid + 1, r);
    mxp[mid] = mnp[mid] = mid, mxp[mid + 1] = mnp[mid + 1] = mid + 1;
    per(i, mid - 1, l) mxp[i] = a[i] > a[mxp[i + 1]] ? i : mxp[i + 1];
    rep(i, mid + 2, r) mxp[i] = a[i] > a[mxp[i - 1]] ? i : mxp[i - 1];
    per(i, mid - 1, l) mnp[i] = a[i] < a[mnp[i + 1]] ? i : mnp[i + 1];
    rep(i, mid + 2, r) mnp[i] = a[i] < a[mnp[i - 1]] ? i : mnp[i - 1];
    sum[mid] = 0;
    per(i, mid - 1, l) sum[i] = sum[i + 1] + (mxp[i] > mnp[i] ? 1 : 0);
    sum[l - 1] = sum[l];
    int j1 = mid, j2 = mid;
    rep(i, mid + 1, r){
        while (j1 >= l && a[mxp[j1]] < a[mxp[i]])--j1;
        while (j2 >= l && a[mnp[j2]] > a[mnp[i]])--j2;
        ans += sum[l] - sum[min(j1, j2) + 1] + (mxp[i] > mnp[i] ? 1 : 0) * (mid - max(j1, j2)) + max(j2 - j1, 0);
    }
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> n;
    rep(i, 1, n) cin >> a[i];
    solve(1, n);
    cout << ans <<  '\n';
    return 0;
}

:::

CF1482E Skyline Photo

Luogu Link。

Description

给定长度为 n 的两个序列 h,b
minpos_{l,r} 为区间 [l,r] 内最小 h_i 的下标。
你需要将序列分为若干个区间,使得 \sum b_{minpos_{l,r}} 最大。
保证 h_i 各不相同。

Solution

首先显然可以 DP。
定义 f_i 为划分完前 i 个并以 i 为一个右端点的最大答案。
特别地,f_0=0

显然有转移 f_i=\max_{1 \le j \le i}f_{j-1}+b_{minpos_{j,i}}

考虑使用分治优化 DP。
为了方便统计,需要枚举 minpos_{l,r} 更新。

由于前面的 f_i 会更新后面的 f_j,需要保障更新 f_jf_i 已经计算完毕。
只需要在分治树上中序遍历即可。

由于在转移方程中可能出现 j=i 的情况,需要在叶子节点更新 f_i=\max(f_i,f_{i-1}+b_i)

对于其他情况,把 minpos_{l,r} 从中点拆开。
记录 pos_ih 的前 / 后缀最小值位置。
可以 O(len) 递推出来。

再维护指针 j,使得 [l,j] 是满足 h_{minpos_{x,mid}}<h_{minpos_{mid+1,i}} 的极长段。

那么 \forall x \in [l,j]minpos_{x,i}=pos_x\forall x \in (j,mid]minpos_{x,i}=pos_i

$x \in (j,mid]$ 的转移就是 $f_i=\max(f_i,f_{x-1}+b_{pos_i})$。维护 $f_{x-1}$ 的后缀最大值即可。 时间 $O(n \log n)$,空间 $O(n)$。 ### Code :::info[Code] ```cpp #include<bits/stdc++.h> using namespace std; #define ll long long #define rep(a,b,c) for(int a=(b);a<=(c);++a) #define per(a,b,c) for(int a=(b);a>=(c);--a) const int N = 3e5 + 5; int n, h[N], b[N], pos[N]; ll f[N], mx1[N], mx2[N]; void solve(int l, int r){ if (l == r) return void(f[l] = max(f[l], f[l - 1] + b[l])); int mid = (l + r) >> 1; solve(l, mid); pos[mid] = mid, pos[mid + 1] = mid + 1; per(i, mid - 1, l) pos[i] = h[pos[i + 1]] > h[i] ? i : pos[i + 1]; rep(i, mid + 2, r) pos[i] = h[pos[i - 1]] > h[i] ? i : pos[i - 1]; mx1[l - 1] = mx2[mid + 1] =-1e18; rep(i, l, mid) mx1[i] = max(mx1[i - 1], f[i - 1] + b[pos[i]]); per(i, mid, l) mx2[i] = max(mx2[i + 1], f[i - 1]); int j = mid; rep(i, mid + 1, r){ while (j >= l && h[pos[j]] > h[pos[i]])--j; f[i] = max(f[i], max(mx1[j], mx2[j + 1] + b[pos[i]])); } solve(mid + 1, r); } int main(){ ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); cin >> n; rep(i, 1, n) cin >> h[i]; rep(i, 1, n) cin >> b[i]; rep(i, 1, n) f[i] =-1e18; solve(1, n); cout << f[n] << '\n'; return 0; } ``` ::: ## P5979 [PA 2014] Druzyny [Luogu Link](https://www.luogu.com.cn/problem/P5979)。 ### Description 给定长度为 $n$ 的序列 $c,d$。 你需要将其分为若干段,每段满足 $\max_{i=l}^r c_i \le r-l+1 \le \min_{i=l}^r d_i$。 求出最大段数和使得段数最大的方案数,方案数对 $10^9+7$ 取模。 ### Solution 由题意,可以 DP。 定义 $f_i$ 为处理了前 $i$ 个,并且 $i$ 为一个右端点的答案,$g_i$ 为 $f_i$ 的方案数。 那么有转移 $f_i=\max_{1 \le j \le i \land \max_{k=j}^i c_k \le i-j+1 \le \min_{k=j}^i d_k} f_{j-1}+1$,$g_i=\sum_{1 \le j \le i \land \max_{k=j}^i c_k \le i-j+1 \le \min_{k=j}^i d_k }[f_i=f_{j-1}+1]g_{j-1}$。 $g_i$ 的转移可以和 $f_i$ 放在一起。 使用二元组 $(f_i,g_i)$ 表示一个位置的值,那么转移就是 $(f_i,g_i) \leftarrow (\max(f_i,f_j+1),[f_i \ge f_j]g_i+[f_i \le f_j]g_j)$。 对于 $\max_{k=j}^i c_k \le i-j+1 \le \min_{k=j}^i d_k$ 的限制,可以把两个最值从中点拆开。 用 $mx_i$ 表示 $c_i$ 的前 / 后缀最大值,$mn_i$ 表示 $d_i$ 的前 / 后缀最小值。 那么转移条件变成 $1 \le j \le i \land i-j+1 \in[\max(mx_i,mx_j),\min(mn_i,mn_j)]$。 变形得到 $i \in [j+mx_j-1,j+mn_j-1] \land j \in [i-mn_i+1,i-mx_i+1]$。 考虑使用数据结构转移。 使用一个线段树维护每一个位置的 $(f_j,g_j)$ 对转移的贡献。 对于每一个 $j$,在 $i=j+mx_j-1$ 时更新线段树上的 $j$ 为 $(f_{j-1},g_{j-1})$,在 $i=j+mn_j-1$ 时更新线段树上的 $j$ 为 $(-\infty,0)$。 升序处理 $i$,每次先加入 $j$,再查询 $[i-mn_i+1,i-mx_i+1]$ 内所有 $(f_j,g_j)$ 的贡献,然后删除 $j$。 线段树的节点合并与转移的更新类似,$(f_x,g_x)$ 与 $(f_y,g_y)$ 合并得到 $(\max(f_x,f_y),[f_x \ge f_y]g_x+[f_x \le f_y]g_y)$。 同样需要注意处理 $i=j$ 的转移。 时间 $O(n\log^2n)$,空间 $O(n)$。 ### Code :::info[Code] ```cpp #include<bits/stdc++.h> using namespace std; #define ll long long #define rep(a,b,c) for(int a=(b);a<=(c);++a) #define per(a,b,c) for(int a=(b);a>=(c);--a) const int N = 1e6 + 5, P = 1e9 + 7, INF = 0x3f3f3f3f; int n; int c[N], d[N]; struct dp { int f, g; } f[N]; dp merge(dp x, dp y){ return {max(x.f, y.f), ((x.f >= y.f) * x.g + (x.f <= y.f) * y.g) % P}; } int mx[N], mn[N]; dp t[N << 2]; #define ls(x) (x<<1) #define rs(x) (x<<1|1) void upd(int p, int pl, int pr, int x, dp v){ if (pl == pr) return void(t[p] = v); int mid = (pl + pr) >> 1; x <= mid ? upd(ls(p), pl, mid, x, v) : upd(rs(p), mid + 1, pr, x, v); t[p] = merge(t[ls(p)], t[rs(p)]); } dp query(int p, int pl, int pr, int l, int r){ if (l > r) return {-INF, 0}; if (l <= pl && pr <= r) return t[p]; int mid = (pl + pr) >> 1; if (r <= mid) return query(ls(p), pl, mid, l, r); if (l > mid) return query(rs(p), mid + 1, pr, l, r); return merge(query(ls(p), pl, mid, l, r), query(rs(p), mid + 1, pr, l, r)); } struct vec { int head[N], nxt[N], val[N], cnt; void add(int x, int y){ nxt[++cnt] = head[x]; head[x] = cnt; val[cnt] = y; } } add,del; void solve(int l, int r){ if (l == r){ if (c[l] == 1) f[l] = merge(f[l], {f[l - 1].f + 1, f[l - 1].g}); return; } int mid = (l + r) >> 1; solve(l, mid); mx[mid] = c[mid], mx[mid + 1] = c[mid + 1]; per(i, mid - 1, l) mx[i] = max(mx[i + 1], c[i]); rep(i, mid + 2, r) mx[i] = max(mx[i - 1], c[i]); mn[mid] = d[mid], mn[mid + 1] = d[mid + 1]; per(i, mid - 1, l) mn[i] = min(mn[i + 1], d[i]); rep(i, mid + 2, r) mn[i] = min(mn[i - 1], d[i]); rep(i, mid + 1, r) add.head[i] = del.head[i] = 0; add.cnt = del.cnt = 0; rep(i, l, mid){ int L = max(mid + 1, i + mx[i] - 1), R = min(i + mn[i] - 1, r); if (L <= R) add.add(L, i), del.add(R, i); } rep(i, mid + 1, r){ for (int j = add.head[i]; j; j = add.nxt[j]){ int x = add.val[j]; upd(1, 1, n, x, f[x - 1]); } int L = max(l, i - mn[i] + 1), R = min(mid, i - mx[i] + 1); if (L <= R){ dp res = query(1, 1, n, L, R); f[i] = merge(f[i], {res.f + 1, res.g}); } for (int j = del.head[i]; j; j = del.nxt[j]){ int x = del.val[j]; upd(1, 1, n, x, {-INF, 0}); } } solve(mid + 1, r); } int main(){ ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); cin >> n; rep(i, 1, n) cin >> c[i] >> d[i]; f[0] = {0, 1}; rep(i, 1, n) f[i] = {-INF, 0}; rep(i, 1, n * 4) t[i] = {-INF, 0}; solve(1, n); if (f[n].f <= 0) cout << "NIE\n"; else cout << f[n].f << ' ' << f[n].g << '\n'; return 0; } ``` :::