前言
这道题我一开始用最长上升子序列做,结果WA了…但其实差不多,就是求解时要变一下.
题目
传送门
题目大意:
在一条土路上有n段(1<=n<=2000),每段都有一个高度
Ai(1<=i<=n)
A
i
(
1
<=
i
<=
n
)
,现在你可以对每一段增加或减少一些土,使得这条路变成单调不上升或单调不下降,求改动土的最小值?
思路
首先,这道题数据有Bug,只需要求单调不下降序列就可以
好了,我们来看看怎么写这道题的DP?
我们就假设要凑不下降序列,那么对于一个位置i来说的话只有两点要考虑:
1.将i连着前面变成不下降的土的花费尽量少。
2.i的高度越低越好。
那我们可以用一个数组b来存储所有出现的数,这里是去重、从小到大排了序的
我们的目的就是对于i来说枚举所有b中元素,找到它对应的花费,那这样就可以进行DP了.
我们定义:
f[i][j]:前i个数不下降,第i个数凑成b[j]时的最小花费
f
[
i
]
[
j
]
:
前
i
个
数
不
下
降
,
第
i
个
数
凑
成
b
[
j
]
时
的
最
小
花
费
f[i][j]=minf[i−1][k]+abs(a[i]−b[j])(1<=i<=n1<=k<=j<=m)
f
[
i
]
[
j
]
=
m
i
n
f
[
i
−
1
]
[
k
]
+
a
b
s
(
a
[
i
]
−
b
[
j
]
)
(
1
<=
i
<=
n
1
<=
k
<=
j
<=
m
)
但我们发现这样写过后是
O(n3)
O
(
n
3
)
的时间复杂度对于数据是过不了的,那我们要优化!
我们用m表示b数组的长度,由于j是递增的,k是不断从1~j,我们就算多了很多次,那我们可以用一个Min来记录1到当前j的
f[i−1][k]
f
[
i
−
1
]
[
k
]
的最小值.我们就优化成:
f[i][j]=Min+abs(a[i]−b[j])(1<=i<=n1<=j<=m)
f
[
i
]
[
j
]
=
M
i
n
+
a
b
s
(
a
[
i
]
−
b
[
j
]
)
(
1
<=
i
<=
n
1
<=
j
<=
m
)
那就可以过了~~
输出答案时注意不是直接输出
f[n][m]
f
[
n
]
[
m
]
,而是输出
minf[n][i](1<=i<=m)
m
i
n
f
[
n
]
[
i
]
(
1
<=
i
<=
m
)
代码
#include<set>
#include<map>
#include<ctime>
#include<queue>
#include<cmath>
#include<cstdio>
#include<climits>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
int read(){
int f=1,x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
return x*f;
}
#define MAXN 2000
#define INF 0x3f3f3f3f
int f[MAXN+5][MAXN+5],a[MAXN+5],b[MAXN+5];
int main(){//前i个数以b[j]结尾的最小花费
int n=read(),m;
for(int i=1;i<=n;i++)
a[i]=read(),b[i]=a[i];
sort(b+1,b+n+1);//b数组排序
m=unique(b+1,b+n+1)-(b+1);//这是一个去重函数
for(int i=1;i<=n;i++){
int Min=INF;//Min优化:f[i-1][k](1<=k<=j)中的最小值
for(int j=1;j<=m;j++){
Min=min(Min,f[i-1][j]);//更新最小值
f[i][j]=Min+abs(a[i]-b[j]);//状态转移
}
}
int ans=INF;//统计答案
for(int i=1;i<=m;i++)
ans=min(ans,f[n][i]);
printf("%d\n",ans);
return 0;
}
题外话
……如果真的改数据的话,我们把存一下当前的最优答案,然后把a数组倒过来,f数组清零,再做一遍就可以了,因为你求了a数组倒序的不下降序列的最小花费就是a数组正序的不上升序列的最小花费