[HAOI2006]数字序列
题目描述
现在我们有一个长度为n的整数序列A。但是它太不好看了,于是我们希望把它变成一个单调严格上升的序列。但是不希望改变过多的数,也不希望改变的幅度太大。
输入输出格式
输入格式:第一行包含一个数n,接下来n个整数按顺序描述每一项的键值。
输出格式:第一行一个整数表示最少需要改变多少个数。
第二行一个整数,表示在改变的数最少的情况下,每个数改变的绝对值之和的最小值。
输入输出样例
说明
【数据范围】
90%的数据n<=6000。
100%的数据n<=35000。
保证所有数列是随机的。
一份讲解的链接
先将数组每一位a[i]减i
这样单调上升就变成了不下降
在给第n+1位加一个正无穷的值(可以做所有子串的结尾,用于统计第2问的答案)
第一问:求最长不下降串长L
答案就是n-L
第二问:首先令f[i]表示1~i的最长不下降长度,g[i]为将1~i变为不下降的代价
对于一对(i,j)且f[i]=f[j]+1
设w(i,j)为将j+1~i变为单调不下降的最小代价
有一个结论:
找到一个断点k
j+1~k全部变成a[j],k+1~i全部变成a[i]
这样一定可以找到这个最小代价
证明见链接
这个复杂度很玄学,最坏O(n^3),但数据是随机的,所以远远达不到
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 using namespace std; 7 typedef long long lol; 8 struct Node 9 { 10 int next,to; 11 }edge[100001]; 12 int L,n,head[40001],num,Min[40001],a[40001]; 13 lol s1[40001],s2[40001],g[40001],f[40001]; 14 void add(int u,int v) 15 { 16 num++; 17 edge[num].next=head[u]; 18 head[u]=num; 19 edge[num].to=v; 20 } 21 int find(int x) 22 { 23 int l=0,r=L,as=0; 24 while (l<=r) 25 { 26 int mid=(l+r)/2; 27 if (Min[mid]<=x) as=mid,l=mid+1; 28 else r=mid-1; 29 } 30 return as; 31 } 32 int main() 33 {int i,j,k; 34 cin>>n; 35 for (i=1;i<=n;i++) 36 { 37 scanf("%d",&a[i]); 38 a[i]-=i; 39 } 40 ++n; 41 a[n]=(1<<30); 42 memset(Min,127,sizeof(Min)); 43 Min[0]=-1<<30;L=0; 44 for (i=1;i<=n;i++) 45 { 46 int t=find(a[i]); 47 f[i]=t+1; 48 L=max(L,t+1); 49 Min[t+1]=min(Min[t+1],a[i]); 50 } 51 cout<<n-L<<endl; 52 for (i=n;i>=0;i--) 53 { 54 add(f[i],i); 55 g[i]=1ll<<60; 56 } 57 a[0]=-1<<30;g[0]=0; 58 for (i=1;i<=n;i++) 59 { 60 for (j=head[f[i]-1];j;j=edge[j].next) 61 { 62 int v=edge[j].to; 63 if (v>i) break; 64 if (a[v]>a[i]) continue; 65 for (k=v;k<=i;k++) 66 s1[k]=abs(a[k]-a[v]),s2[k]=abs(a[k]-a[i]); 67 for (k=v+1;k<=i;k++) 68 s1[k]+=s1[k-1],s2[k]+=s2[k-1]; 69 for (k=v;k<i;k++) 70 g[i]=min(g[i],g[v]+s1[k]-s1[v]+s2[i]-s2[k]); 71 } 72 } 73 cout<<g[n]; 74 }