1049: [HAOI2006]数字序列
Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 1848 Solved: 801
[ Submit][ Status][ Discuss]
Description
现在我们有一个长度为n的整数序列A。但是它太不好看了,于是我们希望把它变成一个单调严格上升的序列。
但是不希望改变过多的数,也不希望改变的幅度太大。
Input
第一行包含一个数n,接下来n个整数按顺序描述每一项的键值。n<=35000,保证所有数列是随机的
Output
第一行一个整数表示最少需要改变多少个数。 第二行一个整数,表示在改变的数最少的情况下,每个数改变
的绝对值之和的最小值。
Sample Input
Sample Output
第一问很简单,对于下标x<y,当且仅当a[y]-a[x]>=y-x时,下标y和x的两个数就可以都不动,否则一定有一个要修改
设b[i] = a[i]-i,F[i]表示以b[i]结尾的LIS长度,答案就是n-F[n]
第二问可以证出对于下标x<y,且F[x]+1=F[y],那么一定存在一个k∈(x, y)满足
(x, k]里的数全部修改成a[x]+1, a[x]+2,a[x]+3,…,a[x]+k-x;
(k, y)里的数全部修改成a[y]-(y-k+1),…,a[y]-2,a[y]-1
即[x, k]内所有的a[]连续,(k, y]内所有的a[]连续时幅度最优
设dp[i]表示前i个数修改成严格递增序列的最小幅度,其中第i个数a[i]不变
那么有dp[i] = min(dp[j]+Mincost(k), j<i && F[j]+1=F[i] && b[j]<=b[i])
其中Mincost也可以直接暴力k∈(x, y),这样复杂度为O(n^3),但是由于数据完全随机,所以能进行转移的(i, j)非常少!
不过为了保证正确,要在数组最前面加一个非常小的数,在数组最后面加一个非常大的数
O(可以过),程序还可以再优化
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define LL long long
LL b[35005], a[35005], bet[35005], F[35005], dp[35005];
int main(void)
{
LL n, i, len, l, j, k, r, m, now, Min;
scanf("%lld", &n);
for(i=1;i<=n;i++)
{
scanf("%lld", &a[i]);
b[i] = a[i]-i;
}
len = 0;
a[0] = b[0] = -1e10;
a[n+1] = b[n+1] = 1e10;
for(i=1;i<=n+1;i++)
{
if(len==0 || bet[len]<=b[i])
bet[++len] = b[i], F[i] = len;
else
{
l = 1, r = len;
while(l<r)
{
m = (l+r)/2;
if(bet[m]<=b[i])
l = m+1;
else
r = m;
}
bet[r] = b[i];
F[i] = r;
}
}
memset(dp, 62, sizeof(dp));
printf("%lld\n", n-(len-1));
dp[0] = 0;
for(i=1;i<=n+1;i++)
{
for(j=0;j<=i-1;j++)
{
if(F[j]+1!=F[i] || b[j]>b[i])
continue;
now = 0;
for(k=j+1;k<=i-1;k++)
now += abs(a[k]-(a[i]-(i-k)));
Min = now;
for(k=j+1;k<=i-1;k++)
{
now = now-abs(a[k]-(a[i]-(i-k)))+abs(a[k]-(a[j]+k-j));
Min = min(Min, now);
}
dp[i] = min(dp[i], dp[j]+Min);
}
}
printf("%lld\n", dp[n+1]);
return 0;
}
/*
5
1 1 1 1 1
*/