学习笔记:分治处理区间最值相关的计数 / DP
CGcgxyXY
·
·
算法·理论
总述
有如下两类与区间最值 / 区间最值位置相关的问题。
第一类:
统计满足一定条件的区间个数 / 区间权值和。
例如 \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] 的区间,可以递归计算。
仅处理跨区间的答案即可。
由于跨区间,显然满足:区间一定包含中点 mid 和 mid+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,然后分讨最值的情况。
- 两个都在左区间。此时 x \in [l,\min(j1,j2)],需要统计的是 \sum_{ x \in [1,\min(j1,j2)]}[mxp_x>mnp_x],额外预处理左区间 [mxp_x>mnp_x] 的前缀和即可。
- 两个都在右区间。此时 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]。
- 最大值在左区间,最小值在右区间。显然不满足条件。
- 最大值在右区间,最小值在左区间。条件显然成立。此时 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_j 时 f_i 已经计算完毕。
只需要在分治树上中序遍历即可。
由于在转移方程中可能出现 j=i 的情况,需要在叶子节点更新 f_i=\max(f_i,f_{i-1}+b_i)。
对于其他情况,把 minpos_{l,r} 从中点拆开。
记录 pos_i 为 h 的前 / 后缀最小值位置。
可以 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;
}
```
:::