【luogu CF1693D】Decinc Dividing(DP)

Decinc Dividing

题目链接:luogu CF1693D

题目大意

给你一个排列,问你有多少个区间满足可以删掉一个单调递减子序列(可以是空的)得到一个单调递增数组。

思路

其实题目就是问你有多少个区间能划分成一个上升子序列和下降子序列。
首先我们考虑枚举左端点,往右扩展的时候 DP,那就是 n 2 n^2 n2 的。
(因为有一个比较显然的性质是如果 [ l , r ] [l,r] [l,r] 不行 [ l , r + 1 ] [l,r+1] [l,r+1] 更加不行,所以固定左端点只有,合法的区间的右端点是一段前缀)

怎么 O ( n ) O(n) O(n) DP呢,一个事情就是你要用一维记录下两个序列的最后一个。
那必然有其中一个是你当前的位置,所以你只需要记录另一个位置即可。
那我们设:
f i , 0 f_{i,0} fi,0 i i i 所在的递增,递减最后一个数最大值
f i , 1 f_{i,1} fi,1 i i i 所在的递减,递增最后一个数最小值
(这个最大和最小是尽可能给后面留空间)

(其中 f i , 0 f_{i,0} fi,0 初始值为 − i n f -inf inf f i , 1 f_{i,1} fi,1 初始值为 i n f inf inf,也代表无解值)
(然后初始化是 f i , 0 = i n f , f i , 1 = − i n f f_{i,0}=inf,f_{i,1}=-inf fi,0=inf,fi,1=inf

然后考虑转移,你就直接看新的数是放在哪个序列,能不能放。
考虑优化,我们试着左端点从右往左,每次新加入一个点会怎样呢?
好像不会咋样。

但是如果你 f i , 0 , f i , 1 f_{i,0},f_{i,1} fi,0,fi,1 DP 出来的结果跟之前一样,那你没必要继续下去,直接去上一轮的结果就行。
因为这个 DP 只会从 f i − 1 , 0 / 1 f_{i-1,0/1} fi1,0/1 来更新 f i , 0 / 1 f_{i,0/1} fi,0/1

那这个记忆化好像也没啥用。
我们试着看看每个位置的取值。
首先 i n f , − i n f inf,-inf inf,inf 都有,那我们再看会不会有别的值。
f i , 0 f_{i,0} fi,0 为例子, f i , 1 f_{i,1} fi,1 同理。

我们找到 i i i 前面最大的 j j j 使得 a j > a j + 1 a_j>a_{j+1} aj>aj+1,那 a j , a j + 1 a_j,a_{j+1} aj,aj+1 不能都在递增的序列中,那就要放到递减的,又因为是最大的 j j j,所以要么是 a j a_j aj 做了最后的值,要么是 a j + 1 a_{j+1} aj+1
那如果没有这个 j j j,那就是初始化 1 1 1 位置的 i n f inf inf
如果无解,就是无解的 − i n f -inf inf

f i , 0 f_{i,0} fi,0 就只有四种取值最多, f i , 1 f_{i,1} fi,1 也一样。
一个显然的事情是随着左端点的左移,这个值如果改变也只会单向的变化(变得更加容易无解)。
所以其实一个位置的值至多边 7 7 7 次,八种情况。

所以我们可以直接暴力从这个位置开始 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;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值