Luogu P2501 [HAOI2006]数字序列

题目
首先把\(a\)改成严格单调上升等于把\(a_i-i\)改成单调不降。
那么第一问可以直接做LIS,答案就是\(n-\)LIS的长度。
同时我们记录一下序列中每个位置结尾的LIS长度。
第二问我们考虑这样一个事实:
对于LIS中相邻的两个数\(a_i,a_j\),这两个数中间的数一定要么\(<a_i\)要么\(>a_j\)
考虑一种修改方案,显然修改后中间的数会呈现阶梯状。
对于任意一个阶梯\((l,r,x)\),如果它上面的数(\([l,r]\)\(>x\)的数)的个数大于下面的数的个数(\([l,r]\)\(<x\)的数),那么我们把它往上移到下一个阶梯的高度一定会更优。
反之我们把它移到下一个阶梯的高度一定会更优。
如果上面和下面的数个数相等,我们随便怎么移动都不会改变这个代价。
因此最终我们一定可以把它移成两个阶梯\((i+1,k,a_i),(k+1,j,a_j)\)
那么对于LIS中相邻的两数\(a_i,a_j\),最优的修改方案一定是找到某个\(k\),把\(a_{i+1}\sim a_k\)改成\(a_i\),把\(a_{k+1}\sim a_j\)改成\(a_j\)
我们枚举这个\(k\)。然后计算。
注意到数据随机,所以LIS的期望长度为\(\log n\),因此复杂度正确。

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;
const int N=35007,inf=0x3f3f3f3f;
int read(){int x=0,c=getchar();while(!isdigit(c))c=getchar();while(isdigit(c))x=x*10+c-48,c=getchar();return x;}
ll min(ll a,ll b){return a<b? a:b;}
int abs(int a){return a<0? -a:a;}
int a[N],num[N],f[N];ll g[N],s[N],t[N];vector<int>E[N];
int main()
{
    int n=read(),i,k,l,len;
    for(i=1;i<=n;++i) a[i]=read()-i;
    a[++n]=inf,a[0]=-inf,memset(num,0x3f,sizeof num),num[0]=-inf,num[1]=a[1],f[1]=len=1,memset(g,0x3f,sizeof g),g[0]=0;
    for(i=2;i<=n;++i) l=upper_bound(num,num+len+1,a[i])-num,len=max(len,l),f[i]=l,num[l]=min(num[l],a[i]);
    for(i=0;i<=n;++i) E[f[i]].pb(i);
    for(i=1;i<=n;++i)
	for(int j:E[f[i]-1])
	{
	    if(j>i||a[j]>a[i]) continue;
	    for(k=j;k<=i;++k) s[k]=abs(a[k]-a[j]),t[k]=abs(a[k]-a[i]);
            for(k=j+1;k<=i;++k) s[k]+=s[k-1],t[k]+=t[k-1];
            for(k=j;k<i;++k) g[i]=min(g[i],g[j]+s[k]-s[j]+t[i]-t[k]);
	}
    printf("%d\n%lld",n-f[n],g[n]);
}
posted @ 2019-10-30 09:15  Shiina_Mashiro  阅读(132)  评论(0编辑  收藏  举报