思路:因为改变的数是同一个,所以最后对LIS的贡献最多只能是1,所以可以先求出最长上升子序列长度,然后每个改变的数,考虑它对LIS的加成是1还是0.
设a[i]表示已第i项结束的最长上升子序列长度,b[i]表示以第i项为起点的最长上升子序列长度。
考虑在0的左边找一个a[i],在0的右边找一个b[j](并且c[i]<c[j]),如果a[i]+b[j]==L,那么将这个0改成其他数字,就有可能使得LIS增加。
下面考虑如何找这样的对数。
首先找这样的对数一定是在0的两边开始找的,如果在同一边,这时没有意义的。
设f[i]表示长度为i的,起点最大的c[i]。找最大的开始点是因为,假设在0的左边有a[i],在0的右边有多个b[j]满足a[i]+b[j]==L,这时我们只有找一个最大的c[j]就行,因为它可以包含其他的。
下面介绍O(n)的方法求出满足的。
设一个区间d[] 如果d[i]>0表示i这个值有效,假设我们找到一个区间[a,b]表示a到b之间的数都可以满足+1,那么我们可以这样考虑,让d[a+1]++,d[b]--,表示在a+1以前都可以满足,而超出b以后,就不行,所以立刻-1.这样我们求一个求前缀和就清楚了,如果大于0,表示满足,==0,表示不满足。
这个处理技巧非常巧妙。具体实现看代码和注释吧。
代码:
#include<bits/stdc++.h>
#define N 100010
#define LL long long
using namespace std;
const LL mod=1e9+7;
int a[N],b[N],c[N],f[N],d[N];
int main()
{
int n;
while (~scanf("%d",&n))
{
for (int i=0;i<=n;i++)a[i]=b[i]=d[i]=f[i]=0;
for (int i=1;i<=n;i++) scanf("%d",&c[i]);
int k=1;
while (c[k]==0 && k<=n) k++;
f[1]=c[k];int t=0;a[k]=1;
if (k<=n)t=1;
for (int i=k+1;i<=n;i++) if (c[i])
{
int j=lower_bound(f+1,f+t+1,c[i])-f;
f[j]=c[i];
a[i]=j; //end
if (j==t+1) t++;
}
k=n;
while (c[k]==0 && k) k--;
f[1]=-c[k];t=0; b[k]=1;
if (k>0)t=1;
for (int i=k-1;i>0;i--) if (c[i])
{
int j=lower_bound(f+1,f+t+1,-c[i])-f;
f[j]=-c[i];
b[i]=j; //begin
if (j==t+1) t++;
}
for (int i=0;i<=n;i++)f[i]=0;
k=n;
//f[0]=n+1;
while(k>0)
{
int y=k;
for (;k>0;k--)
{
if (!c[k]){f[0]=n+1;break;}
int u=t-a[k];
if (f[u]-1>c[k]) // 找到符合要求的,将其进行标记
{
d[c[k]+1]++;
d[f[u]]--;
}
}
for (int i=y;i>k;i--) if (f[b[i]]<c[i]) f[b[i]]=c[i];//更新f的最大值
if (f[t]-1>c[k] && k) //如果找到一个,以它为起点的LIS长度==L,那么在这个0的位置放上1到f[t],必然可以增加LIS.
{
d[c[k]+1]++;
d[f[t]]--;
}
k--;
}
for (int i=1;i<=n;i++) d[i]+=d[i-1];
LL ans=0;
for (int i=1;i<=n;i++)if (!d[i])ans+=(LL) i*(t);else ans+=(LL) i*(t+1);
printf("%I64d\n",ans);
}
}