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)