1049: [HAOI2006]数字序列
Time Limit: 10 Sec Memory Limit: 162 MB
Submit: 979 Solved: 389
Description
现在我们有一个长度为n的整数序列A。但是它太不好看了,于是我们希望把它变成一个单调严格上升的序列。但是不希望改变过多的数,也不希望改变的幅度太大。
Input
第一行包含一个数n,接下来n个整数按顺序描述每一项的键值。
Output
第一行一个整数表示最少需要改变多少个数。 第二行一个整数,表示在改变的数最少的情况下,每个数改变的绝对值之和的最小值。
Sample Input
4
5 2 3 5
Sample Output
1
4
HINT
【数据范围】
90%的数据n<=6000。
100%的数据n<=35000。
保证所有数列是随机的。
神奇的dp。
对于第一问:
改变的数最少即让不变的数最多,那么类似于求最长上升子序列,只是要求增加:f[i]=max(f[j]+1) (a[i]-a[j]>=i-j)
把a[i]-a[j]>=i-j移项:
a[i]-i>=a[j]-j
因此把每一个数都减去i,直接求LIS即可(用nlogn的算法)
对于第二问(注意:此时每个数已经减去i了!!):
我们要求使得a数组单调不减的最少改变量。
#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <cstdlib> #include <cmath> #define M 35005 #define LL long long using namespace std; LL g[M],s1[M],s2[M]; struct edge { int y,ne; }e[M]; int m[M],cnt,h[M],a[M],n,f[M]; void read(int &tmp) { tmp=0; char ch=getchar(); int fu=1; for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') fu=-1; for (;ch>='0'&&ch<='9';ch=getchar()) tmp=tmp*10+ch-'0'; tmp*=fu; } void Solve1() { memset(m,127,sizeof(m)); int ma=1; m[1]=a[1],f[1]=1; for (int i=2;i<=n;i++) { int l=1,r=ma,ans=0; while (l<=r) { int mid=(l+r)>>1; if (m[mid]<=a[i]) ans=mid,l=mid+1; else r=mid-1; } f[i]=ans+1; ma=max(ma,f[i]); m[f[i]]=min(m[f[i]],a[i]); } printf("%d\n",n-f[n]); } void Insert(int x,int y) { e[++cnt].y=y; e[cnt].ne=h[x]; h[x]=cnt; } void Solve2() { for (int i=n;i>=0;i--) { Insert(f[i],i); g[i]=1LL<<60; } g[0]=0,a[0]=-1<<30; for (int i=1;i<=n;i++) for (int j=h[f[i]-1];j;j=e[j].ne) { int p=e[j].y; if (p>i) break; if (a[p]>a[i]) continue; for (int k=p;k<=i;k++) s1[k]=abs(a[p]-a[k]),s2[k]=abs(a[i]-a[k]); for (int k=p+1;k<=i;k++) s1[k]+=s1[k-1],s2[k]+=s2[k-1]; for (int k=p;k<i;k++) g[i]=min(g[i],g[p]+s1[k]-s1[p]+s2[i]-s2[k]); } cout<<g[n]<<endl; } int main() { read(n); for (int i=1;i<=n;i++) read(a[i]),a[i]-=i; a[++n]=1<<30; Solve1(); Solve2(); return 0; }
感悟:
1.WA无数次:第一问求错,求整个序列的最长不下降序列,我直接把f[n]当做答案了,应该再加一句a[++n]=inf,f[n]才是答案
2.第二问是贪心,发现贪心似乎都是用反证法来证明的。。
时间: 2024-10-17 03:22:42