返回顶部

CF446A DZY Loves Sequences 题解

题目大意

给一个长度为n的序列 \(a_n\)

定义 \(a_i\)\(a_j (1<=i<=j<=n)\) 的长度为 \(j-i+1\),你可以最多更改一个数字,求最长的严格递增子段。


题解

一 . 分析

(注意到原序列的每个 \(a_i\) 都是正整数,而我们修改时可以将其变为任意整数。)

定义 \(l[i]\) 数组:\(a_i\) 结尾的最长子段长度。

定义 \(r[i]\) 数组:\(a_i\) 开头的最长子段长度。

情况一

把修改点 \(a_i\) 作为关键点,将左右两侧合并,可以发现:

我们只需要判断 \(a_{i-1}\)\(a_{i+1}\) 的大小关系。

而两者想要连起来,首先需要满足单调递增,还要满足它们之间存在插入 \(a_i\) 的空隙(严格递增序列)。

\(a_{i+1} - a_{i-1} >1\) 时,满足递增同时可以插入 \(a_i\)

\(ans=\max(ans,l[i]+r[i]+1),(a_{i+1} - a_{i-1} >1)\)

但是只靠这个判断是不全面的:

情况二

譬如给出序列 \(1\)\(2\)\(3\)\(4\)\(1\)\(2\)\(3\)

按照情况一,以 \(4\) 后的 \(1\) 为修改点。\(1-4<0\),也就是说对 \(1\) 不再修改,答案为 \(4\)。而实际上的最长序列是将 \(4\) 之后的 \(1\) 修改为大于 \(4\) 的数,最长序列长度就是 \(5\)

所以情况二将关键点本身接续到其中一端刷新答案。

\(ans=\max(ans,l[i]+1,r[i]+1),(a[i+1]-a[i-1]<=1)\);

同时注意情况一优先级高于情况二

总结

存在两种修改方式,以代码块形式呈现:

for(int i=1;i<=n;i++)//以每个点分别作为关键点
	{
		if(a[i+1]-a[i-1]>1)//优先 情况一:合并
		    ans=max(ans,l[i-1]+r[i+1]+1);
		else//否则 情况二:接续
		{
			ans=max(ans,l[i-1]+1);
			ans=max(ans,r[i+1]+1);
		}
		    
	}

二.实现

读入 \(a[i]\) 数组时顺便处理 \(l[i]\),读入完成后倒序处理 \(r[i]\), 然后对每个数进行上述判断更新 \(ans\)

代码实现:

#include<iostream>
#include<cstdio>
#define int long long
using namespace std;
const int maxn=1e5+7;
int a[maxn];
int n,l[maxn],r[maxn];

signed main()
{
	 scanf("%lld",&n);
    if(n==1) 
	{
	     printf("1\n");
		 return;	
	}
    a[0]=-100; l[0]=0;//预处理
    for(int i=1;i<=n;i++)//顺便处理l[i]
    {
    	scanf("%lld",&a[i]);
    	if(a[i]>a[i-1] && i>1)
    	{
    		l[i]=l[i-1]+1;
    	}//比上一个大,延续上一个的序列
    	else l[i]=1;//否则以自己为开头重开序列长度
    }
    r[n]=1; r[n+1]=0; //预处理
    for(int i=n-1;i>0;--i)//反着来
	{
		if(a[i]<a[i+1])
		{
			r[i]=r[i+1]+1;
		}
		else r[i]=1;//和l[i] 处理思路一致
	} 
	int ans=1;
	for(int i=1;i<=n;i++)//以每个点分别作为关键点
	{
		if(a[i+1]-a[i-1]>1)//优先 情况一:合并
		    ans=max(ans,l[i-1]+r[i+1]+1);
		else//否则 情况二:接续
		{
			ans=max(ans,l[i-1]+1);
			ans=max(ans,r[i+1]+1);
		}
		    
	}
   printf("%lld\n",ans);
	return 0;
}

这是我的第一篇题解(由于格式问题麻烦了管理大大好多次,希望这次能过QAQ)

posted @ 2022-06-29 20:42  魔幻世界魔幻人生  阅读(36)  评论(0)    收藏  举报