Two Merged Sequences(CF 1144 G)

11 篇文章 0 订阅

前言

在做其它题的时候脑补到过这个题意,没想到还真有这样的题

题目相关

链接

题目大意

给一个序列,问是否能将这个序列完全划分成一个上升子序列和下降子序列

数据范围

n ≤ 2 ⋅ 1 0 5 n\le2·10^5 n2105

题解

有个不错的思路:
找到最大值,讨论他是上升序列的最后一个元素还是下降序列的第一个元素
如果是上升序列的最后一个元素,那么需要满足两个条件:
1.其后面的元素都是在下降序列中的
2.其前面的元素的下降序列中的最后一个元素大于后面的元素
如果这是下降序列的第一个元素,将数组翻转即可
我们发现,对于第一个条件的满足与否,就可以判断是哪种情况

  • 两边都不满足:无解
  • 有一边满足:可能是这种情况,转化子问题递归计算
  • 两边都满足:直接解决问题

递归计算时,我们发现有一个额外的限制,我们要先把这个限制弄掉
实际操作中,限制可以直接把连续的一段删除并产生新的限制,若新的限制无效了,那么就是子问题了,再递归计算即可
以上的做法可能要用到线段树,并且要在线段树上二分之类的,不是很好写
而且复杂度为 O ( n l o g n ) \mathcal O(nlogn) O(nlogn)


然后,这里隆重推出 O ( n ) \mathcal O(n) O(n)的dp做法
看来是我dp没学好,咋开始没想到呢?
我们定义状态:
f i , 0 f_{i,0} fi,0为第 i i i个数在下降子序列上,此时上升子序列最大值的最小值
f i , 1 f_{i,1} fi,1为第 i i i个数在上升子序列上,此时下降子序列最小值的最大值
转移的话直接讨论一下上个状态和下个状态放哪种就好了
然后就可以直接判定
输出方案的话记录每一个状态的前继状态即可
清真+好写+复杂度低

代码

#include<bits/stdc++.h>
typedef long long ll;
#define rg register
template <typename T> inline void read(T&x){char cu=getchar();x=0;bool fla=0;while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}while(isdigit(cu))x=x*10+cu-'0',cu=getchar();if(fla)x=-x;}
template <typename T> inline void printe(const T x){if(x>=10)printe(x/10);putchar(x%10+'0');}
template <typename T> inline void print(const T x){if(x<0)putchar('-'),printe(-x);else printe(x);}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline T max(const T a,const T b){return a>b?a:b;}
const int inf=0x7f7f7f7f;
int n,a[200001],opt[200001];
int f[200001][2],g[200001][2];
int main()
{
	read(n);
	for(rg int i=1;i<=n;i++)read(a[i]);
	f[1][0]=-inf,f[1][1]=inf;
	for(rg int i=2;i<=n;i++)
	{
		f[i][0]=inf,f[i][1]=-inf;
		if(a[i-1]>a[i]&&f[i][0]>f[i-1][0])
		{
			f[i][0]=f[i-1][0];
			g[i][0]=0;
		}
		if(f[i-1][0]<a[i]&&f[i][1]<a[i-1])
		{
			f[i][1]=a[i-1];
			g[i][1]=0;
		}
		if(a[i-1]<a[i]&&f[i][1]<f[i-1][1])
		{
			f[i][1]=f[i-1][1];
			g[i][1]=1;
		}
		if(f[i-1][1]>a[i]&&f[i][0]>a[i-1])
		{
			f[i][0]=a[i-1];
			g[i][0]=1;
		}
	}
	if(f[n][0]!=inf)
	{
		puts("YES");
		for(rg int i=n,op=0;i>=1;op=g[i][op],i--)opt[i]=op;
		for(rg int i=1;i<=n;i++)print(opt[i]^1),putchar(' ');
		return 0;
	}
	if(f[n][1]!=-inf)
	{
		puts("YES");
		for(rg int i=n,op=1;i>=1;op=g[i][op],i--)opt[i]=op;
		for(rg int i=1;i<=n;i++)print(opt[i]^1),putchar(' ');
		return 0;
	}
	puts("NO");
	return 0;
}

总结

挺巧妙的一道题
有时候换种做法说不定会好写很多

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值