Description
现在我们有一个长度为n的整数序列A。但是它太不好看了,于是我们希望把它变成一个单调严格上升的序列。
但是不希望改变过多的数,也不希望改变的幅度太大。
Input
第一行包含一个数n,接下来n个整数按顺序描述每一项的键值。n<=35000,保证所有数列是随机的
Output
第一行一个整数表示最少需要改变多少个数。 第二行一个整数,表示在改变的数最少的情况下,每个数改变
的绝对值之和的最小值。
Sample Input
4
5 2 3 5
Sample Output
1
4
分析
对于第一问,我们正着做比较麻烦,所以考虑补集,那么问题就转化为了使得不用改变的点最多。
设a为原数组,对于
i,j,i<j
,使得
i,j
不被修改的条件是:
aj−ai≥j−i
,当满足该条件时,我们就可以通过修改中间
j−i−1
个数来满足整个序列的严格递增,由此,我们很容易就可以得到dp方程:
f[i]=maxi−1j=1(f[j]+1(ai−aj>=i−j))
我们对 ai−aj≥i−j 进行移项可得: ai−i>aj−j ,我们定义 bi=ai−i ,那么这个问题就转换成了求b的最长不下降子序列。
对于第二问,从第一问中我们可以得到,如果 j 能转移到
g[i]=minf[j]+1=f[i](g[j]+w[j+1,i])
让 a 数组单调递增,等价于让 b 数组单调不降,考虑让 b 数组单调不降的 最小花费
由于有 f[j]+1=f[i] ,所以 ∀j<k<i,bk≤bj 或者 bi≤bk , 现在一个结 论是, w[j,i] 的方案,一定会以某个 k 为分界使得
使用数学归纳法。 一两个数显然成立。
假设 k 个数,以下标为横坐标,最优方案下的 b 值为纵坐标,按照那个结 论分布好了,很像是一上一下两条水平线,左边一段在下面那根,右边一段在 上面那根,现在冒出一个奇葩点 p,他在两个水平线之间
显然他的 a 值在上面的上方或下面的下方,接下来就是一堆分情况讨论的 事情而已。首先他不能做在上面点和在下面点的临界点,不然的话他往上走或 往下走总能找到更好地。也就是说,他现在要么夹在一坨在下方的点中间在他 们上方,要么在一坨在上边界的点中间却在下方
两种情况很类似,讨论第一种即可。那么他一定会导致他右边这些在下方的 点为了满足合法性不得不飞到他上面,由于这个点不在的时候是优方案,所以 我们知道这右下方几个点往上跑 1 格会使变大。
既然这样的话,如果这个讨厌的点往下走会使花费变少,他肯定要往下走。 否则的话,就说明他往下走 x 格要增加 x 的花费。但是人多力量打,他走一格 才一的花费,敌不过后面一坨点
其实这样算的话也是不全面的,因为后面这些点有可能随他乱动代价都一 样,这时候大家都往上跑走到最最上面,花费还是变少了
于是皆大欢喜
代码
#include <bits/stdc++.h>
#define N 35005
#define ll long long
struct NOTE
{
int to,next;
}e[N];
int cnt;
int next[N];
int n;
int L;
int mxDp[N];
int a[N];
int f[N];
ll g[N];
ll s1[N], s2[N];
int read()
{
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
int find(int x)
{
int l = 1, r = L, res = 0;
while (l <= r)
{
int mid = (l + r) >> 1;
if (mxDp[mid] <= x)
res = mid, l = mid + 1;
else r = mid - 1;
}
return res;
}
void dp()
{
memset(mxDp,127,sizeof(mxDp));
mxDp[0] = -1 << 30;
for (int i = 1; i <= n; i++)
{
int tmp = find(a[i]);
f[i] = tmp + 1;
L = std::max(tmp + 1, L);
mxDp[tmp + 1] = std::min(mxDp[tmp + 1], a[i]);
}
}
void insert(int x,int y)
{
e[++cnt] = (NOTE){y,next[x]}; next[x] = cnt;
}
void sloveQ2()
{
for (int i = n; i >= 0; i--)
{
insert(f[i],i);
g[i] = 1LL << 60;
}
g[0] = 0; a[0] = -1 << 30;
for (int x = 1; x <= n; x++)
for (int i = next[f[x] - 1]; i; i = e[i].next)
{
int p = e[i].to;
if (p > x)
break;
if (a[p] > a[x])
continue;
for (int j = p; j <= x; j++)
s1[j] = abs(a[p] - a[j]), s2[j] = abs(a[x] - a[j]);
for (int j = p + 1; j <= x; j++)
s1[j] += s1[j - 1], s2[j] += s2[j - 1];
for (int j = p; j < x; j++)
g[x] = std::min(g[x], g[p] + s1[j] - s1[p] + s2[x] - s2[j]);
}
}
int main()
{
n = read();
for (int i = 1; i <= n; i++)
a[i] = read() - i;
a[++n] = 1 << 30;
dp();
sloveQ2();
printf("%d\n%lld\n",n - f[n], g[n]);
}