题意:给你一个长度为n(n<=2000)序列,问将其变成单调不递增或单调不递减的序列的最小改动的值为多少?
思路:两种单调性情况,求得一种,把数组反序再求一遍另一种就好了。可以想得到DP,我们就一步步往下考虑。
首先定义状态
dp[i][j]
d
p
[
i
]
[
j
]
表示前
i
i
个数字以 这个数字结尾的时候的答案。
不难想到状态转移方程
dp[i][j]=min(dp[i−1][k])+abs(j−a[i]),(1≤i≤n)(0≤k≤j)(0≤j≤1e9)
d
p
[
i
]
[
j
]
=
m
i
n
(
d
p
[
i
−
1
]
[
k
]
)
+
a
b
s
(
j
−
a
[
i
]
)
,
(
1
≤
i
≤
n
)
(
0
≤
k
≤
j
)
(
0
≤
j
≤
1
e
9
)
其中 a 是题目中给的数列,答案为
min(dp[n][j]),(0≤j≤1e9)
m
i
n
(
d
p
[
n
]
[
j
]
)
,
(
0
≤
j
≤
1
e
9
)
。
j
j
太大了想办法搞一下,因为 很小只有2000,任何时候
j
j
的最优情况只可能是 a 数组中的数字。因为对于原序列的某个数字,一定要变成该序列中的某个数字,才是最优解。(好像是废话…)。那么 的取值范围就大大的缩小了,变为原序列中的某个数字,只有2000个,因为求解的时候我们需要
k≤j
k
≤
j
,所以我们把这个
j
j
的取值搞到一个新的数组 中,进行排序,其实这里就是对
j
j
离散化了一下。
所以,dp方程变为:
我们现在发现仍然需要3重for,复杂度为
n3
n
3
会超时,我们不妨先把这个想法敲一下,然后看哪里能优化。
超时版!!!:
//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 2005;
const int INF = 0x3f3f3f3f;
int n, a[MAXN], b[MAXN], dp[MAXN][MAXN];
int work()
{
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
int MIN = INF;
for (int k = 1; k <= j; k++)
{
MIN = min(MIN, dp[i-1][k]);
}
dp[i][j] = MIN + abs(a[i]-b[j]);
}
}
int ans = INF;
for (int i = 1; i <= n; i++) ans = min(ans, dp[n][i]);
return ans;
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
b[i] = a[i];
}
sort(b+1, b+1+n);
int ans = INF;
ans = min(ans, work());
reverse(b+1, b+1+n);//翻转再求
ans = min(ans, work());
printf("%d\n", ans);
return 0;
}
/*
7
1 3 2 4 5 3 9
*/
我们发现好像最内层的for循环没起到很大的作用,只是求了一个最小值,且 k 是随着 j 在变化的,j 增加1,k就要从1 遍历到 j ,然而我们之前都求过1~(j-1)的最小值了只差跟
dp[i−1][j]
d
p
[
i
−
1
]
[
j
]
的比较一下。我们其实完全可以用一个变量MIN维护
min(dp[i−1][k])(1≤k≤j)
m
i
n
(
d
p
[
i
−
1
]
[
k
]
)
(
1
≤
k
≤
j
)
这个值,将 k 和 j 的循环融合到一起。
这样就完全OK了,复杂度为
n2
n
2
,空间复杂度为
n2
n
2
。
AC代码:
//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 2005;
const int INF = 0x3f3f3f3f;
int n, a[MAXN], b[MAXN], dp[MAXN][MAXN];
int work()
{
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= n; i++)
{
int MIN = INF;
for (int j = 1; j <= n; j++)
{
MIN = min(MIN, dp[i-1][j]);
dp[i][j] = abs(a[i]-b[j])+MIN;
}
}
int ans = INF;
for (int i = 1; i <= n; i++) ans = min(ans, dp[n][i]);
return ans;
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
b[i] = a[i];
}
sort(b+1, b+1+n);
int ans = INF;
ans = min(ans, work());
reverse(b+1, b+1+n);
ans = min(ans, work());
printf("%d\n", ans);
return 0;
}
/*
7
1 3 2 4 5 3 9
*/
虽然已经AC了,但是还能在优化,每次dp[i][j]的状态只和dp[i-1][j] 有关,我们可以用滚动数组优化一下空间,将空间复杂度变为
n
n
<script type="math/tex" id="MathJax-Element-19">n</script>
AC代码
//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 2005;
const int INF = 0x3f3f3f3f;
int n, a[MAXN], b[MAXN], dp[2][MAXN];
int work()
{
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= n; i++)
{
int MIN = INF;
for (int j = 1; j <= n; j++)
{
MIN = min(MIN, dp[(i-1)%2][j]);
dp[i%2][j] = abs(a[i]-b[j])+MIN;
}
}
int ans = INF;
for (int i = 1; i <= n; i++) ans = min(ans, dp[n%2][i]);
return ans;
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
b[i] = a[i];
}
sort(b+1, b+1+n);
int ans = INF;
ans = min(ans, work());
reverse(b+1, b+1+n);
ans = min(ans, work());
printf("%d\n", ans);
return 0;
}
/*
7
1 3 2 4 5 3 9
*/