Decinc Dividing
题目链接:luogu CF1693D
题目大意
给你一个排列,问你有多少个区间满足可以删掉一个单调递减子序列(可以是空的)得到一个单调递增数组。
思路
其实题目就是问你有多少个区间能划分成一个上升子序列和下降子序列。
首先我们考虑枚举左端点,往右扩展的时候 DP,那就是 n2n^2n2 的。
(因为有一个比较显然的性质是如果 [l,r][l,r][l,r] 不行 [l,r+1][l,r+1][l,r+1] 更加不行,所以固定左端点只有,合法的区间的右端点是一段前缀)
怎么 O(n)O(n)O(n) DP呢,一个事情就是你要用一维记录下两个序列的最后一个。
那必然有其中一个是你当前的位置,所以你只需要记录另一个位置即可。
那我们设:
fi,0f_{i,0}fi,0:iii 所在的递增,递减最后一个数最大值
fi,1f_{i,1}fi,1:iii 所在的递减,递增最后一个数最小值
(这个最大和最小是尽可能给后面留空间)
(其中 fi,0f_{i,0}fi,0 初始值为 −inf-inf−inf,fi,1f_{i,1}fi,1 初始值为 infinfinf,也代表无解值)
(然后初始化是 fi,0=inf,fi,1=−inff_{i,0}=inf,f_{i,1}=-inffi,0=inf,fi,1=−inf)
然后考虑转移,你就直接看新的数是放在哪个序列,能不能放。
考虑优化,我们试着左端点从右往左,每次新加入一个点会怎样呢?
好像不会咋样。
但是如果你 fi,0,fi,1f_{i,0},f_{i,1}fi,0,fi,1 DP 出来的结果跟之前一样,那你没必要继续下去,直接去上一轮的结果就行。
因为这个 DP 只会从 fi−1,0/1f_{i-1,0/1}fi−1,0/1 来更新 fi,0/1f_{i,0/1}fi,0/1。
那这个记忆化好像也没啥用。
我们试着看看每个位置的取值。
首先 inf,−infinf,-infinf,−inf 都有,那我们再看会不会有别的值。
以 fi,0f_{i,0}fi,0 为例子,fi,1f_{i,1}fi,1 同理。
我们找到 iii 前面最大的 jjj 使得 aj>aj+1a_j>a_{j+1}aj>aj+1,那 aj,aj+1a_j,a_{j+1}aj,aj+1 不能都在递增的序列中,那就要放到递减的,又因为是最大的 jjj,所以要么是 aja_jaj 做了最后的值,要么是 aj+1a_{j+1}aj+1。
那如果没有这个 jjj,那就是初始化 111 位置的 infinfinf。
如果无解,就是无解的 −inf-inf−inf。
那 fi,0f_{i,0}fi,0 就只有四种取值最多,fi,1f_{i,1}fi,1 也一样。
一个显然的事情是随着左端点的左移,这个值如果改变也只会单向的变化(变得更加容易无解)。
所以其实一个位置的值至多边 777 次,八种情况。
所以我们可以直接暴力从这个位置开始 DP,如果 DP 到的值跟之前的一样就直接用之前的位置作为答案进行贡献。
代码
#include<cstdio>
#include<iostream>
#define ll long long
#define INF (0x3f3f3f3f3f3f3f3f)using namespace std;const int N = 2e5 + 100;
int n, a[N], f[N][2], lst;
ll ans;
//0:i递增,递减最后一个数最大值
//1:i递减,递增最后一个数最小值 int slove(int i) {f[i][0] = INF; f[i][1] = -INF; int tmp = INF;for (int j = i + 1; j <= n; j++) {int v0 = -INF, v1 = INF;if (f[j - 1][0] != -tmp) {if (a[j] > a[j - 1]) v0 = max(v0, f[j - 1][0]);if (a[j] < f[j - 1][0]) v1 = min(v1, a[j - 1]);}if (f[j - 1][1] != tmp) {if (a[j] < a[j - 1]) v1 = min(v1, f[j - 1][1]);if (a[j] > f[j - 1][1]) v0 = max(v0, a[j - 1]);}if (v0 == f[j][0] && v1 == f[j][1]) break;f[j][0] = v0; f[j][1] = v1;if (f[j][0] == -tmp && f[j][1] == tmp) {lst = j - 1;return j - i;}}return lst - i + 1;
}int main() {scanf("%d", &n);for (int i = 1; i <= n; i++) scanf("%d", &a[i]);lst = n;for (int i = n; i >= 1; i--)ans += slove(i);printf("%lld", ans);return 0;
}