Decinc Dividing
对于一个序列 \(A\),若其可以被拆分成可空子序列 \(B\) 和可空子序列 \(C\),使得:
-
序列 \(B\) 单调递增
-
序列 \(C\) 单调递减
则我们称序列 \(A\) 是好的。
给定长度为 \(n\) 的排列 \(P\),求 \(P\) 中好的子段数量。
\(1 \leq n \leq 2 \times 10^5\),\(1 \leq p_i \leq n\)。
先考虑对于单个序列 \(A\) 如何判断合法性。由于题目限制,\(A\) 中的元素两两不同。

考虑从前往后贪心地分配,不妨设 \(a_i\) 被划进递增子序列,则此时的递减子序列结尾应当越大越好。故我们设 \(f_i\) 表示在前 \(i\) 个数中,将 \(a_i\) 划进递增子序列时,递减子序列末尾的最大值。同理设 \(g_i\) 表示在前 \(i\) 个数中,将 \(a_i\) 划进递减子序列时,递增子序列末尾的最小值。
转移是容易的,我们可以做到 \(O(n)\) 判断一个子段是否合法,总时间复杂度为 \(O(n^3)\)。
注意到对于排列 \(P\) 的子段 \([l,r]\) 和 \([l',r']\),其中 \([l',r'] \subseteq [l,r]\)。若 \([l,r]\) 是好的,则 \([l',r']\) 必然也是好的。故我们可以使用双指针确定合法的 \([l,r]\) 对,时间复杂度 \(O(n^2)\)。
考虑对于一个固定的 \(l\),我们可以得到固定的序列 \(f\) 和固定的序列 \(g\) 。我们现在在不断改变 \(l\),则需要考虑序列 \(f\) 和序列 \(g\) 的变化,也就是讨论 \(f_i\) 和 \(g_i\) 的取值。
我们声称对于 \(f_i\) 和 \(g_i\),可能的取值只有 \(O(1)\) 种。
考虑对于 \(j < j+1\),\(p_j > p_{j+1}\),这两者必然有至少一个出现在递减子序列中。故对于满足条件的最大的 \(j\),\(p_j\) 和 \(p_{j+1}\) 都有可能对 \(f_i\) 产生贡献,且其一定最大,也就是说我们证明了 \(f_i\) 和 \(g_i\) 的取值只有 \(O(1)\) 种。
同时我们知道 \(f_i\) 的取值是只和 \(f_{i-1}\) 和 \(g_{i-1}\) 有关的,且随着 \(l\) 增大,\(f_i\) 的值也是不断增大的。同理,随着 \(l\) 增大,\(g\) 的值也是不断减小的。这意味着在移动 \(l\) 的过程中,动态更新 dp 数组,若 \(f_i\) 和 \(g_i\) 都没有变化就停止,此时复杂度为 \(O(n)\),可以通过。
//Ad astra per aspera
#include<iostream>
#include<cstdio>
#include<set>
using namespace std;
const int MAX=0x3f3f3f3f,MIN=-0x3f3f3f3f;
int p[200010],f[200010],g[200010];
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&p[i]);
f[i]=MIN;
g[i]=MAX;
}
int maxn=0;
long long ans=0;
for(int i=1;i<=n;i++){
f[i]=MAX;
g[i]=MIN;
maxn=max(maxn,i);
for(int j=i+1;j<=n;j++){
int lst_f=f[j],lst_g=g[j];
if(p[j-1]<p[j]){
f[j]=max(f[j],f[j-1]);
}
if(g[j-1]<p[j]){
f[j]=max(f[j],p[j-1]);
}
if(p[j-1]>p[j]){
g[j]=min(g[j],g[j-1]);
}
if(f[j-1]>p[j]){
g[j]=min(g[j],p[j-1]);
}
if(f[j]!=MIN || g[j]!=MAX){
maxn=max(maxn,j);
}
if(lst_f==f[j] && lst_g==g[j]){
break;
}
}
ans+=maxn-i+1;
}
printf("%lld",ans);
return 0;
}

浙公网安备 33010602011771号