[dp][瞎搞] 洛谷 P2501 数字序列
题目描述
现在我们有一个长度为n的整数序列A。但是它太不好看了,于是我们希望把它变成一个单调严格上升的序列。但是不希望改变过多的数,也不希望改变的幅度太大。
输入输出格式
输入格式:
第一行包含一个数n,接下来n个整数按顺序描述每一项的键值。
输出格式:
第一行一个整数表示最少需要改变多少个数。
第二行一个整数,表示在改变的数最少的情况下,每个数改变的绝对值之和的最小值。
输入输出样例
说明
【数据范围】
90%的数据n<=6000。
100%的数据n<=35000。
保证所有数列是随机的。
题解
- 我们可以把序列中的每个数减去他们的位置构成一个新的序列,a[i]-=i
- 对于第一问,我们可以求最多有多少个点不用修改,就是最长不下降子序列,答案就是n-最长不下降子序列的长度
- 对于第二问,就是求把这个序列转换成最长不下降子序列的最小代价
- 证明对于[i,j]的一段区间并且满足f[i]+1=f[j]和a[i]<=a[j]
- 我们把它变成不下降的最小代价一定是从中间取一个点t,我们把[i+1,t]全都变成a[i],[t+1,j-1]全都变成a[j]
- 大爷的证明如下:
- 这样我们枚举t就好了,因为随机数据嘛,应该可以过的
代码
1 #include <cstdio> 2 #include <cmath> 3 #include <iostream> 4 using namespace std; 5 #define inf 210000000 6 const int N=35010; 7 int n,cnt,top,a[N],Q[N],f[N],head[N]; 8 long long g[N],x[N],y[N]; 9 struct edge {int to,from;}e[N]; 10 void insert(int x,int y) { e[++cnt].to=y,e[cnt].from=head[x],head[x]=cnt; } 11 int main() 12 { 13 scanf("%d",&n); 14 for (int i=1;i<=n;i++) scanf("%d",&a[i]),a[i]-=i; 15 a[++n]=inf,Q[0]=-inf; 16 for (int i=1;i<=n;i++) 17 if (a[i]>=Q[top]) Q[++top]=a[i],f[i]=top; 18 else 19 { 20 int l=1,r=top; 21 while (l<=r) 22 { 23 int mid=l+r>>1; 24 if (a[i]>=Q[mid]) l=mid+1; else r=mid-1; 25 } 26 Q[l]=a[i],f[i]=l; 27 } 28 printf("%d\n",n-top); 29 for (int i=n;~i;i--) g[i]=inf*1e5,insert(f[i],i); 30 a[0]=-inf,g[0]=0; 31 for (int i=1;i<=n;i++) 32 for (int j=head[f[i]-1];j;j=e[j].from) 33 { 34 if (e[j].to>i) break; 35 if (a[e[j].to]>a[i]) continue; 36 x[e[j].to-1]=y[e[j].to-1]=0; 37 for (int k=e[j].to;k<=i;k++) x[k]=x[k-1]+abs(a[e[j].to]-a[k]),y[k]=y[k-1]+abs(a[i]-a[k]); 38 for (int k=e[j].to;k<i;k++) g[i]=min(g[i],g[e[j].to]+x[k]-x[e[j].to]+y[i]-y[k]); 39 } 40 printf("%lld",g[n]); 41 }