题目描述
香蕉川由座香蕉山组成,第i座山有它的高度。小Z准备从左到右爬这里的恰好座香蕉山,但他不希望山的高度起伏太大,太过颠簸,会让本就体育不好的他过于劳累。所以他定义了爬山的劳累度是所有爬的相邻的两座山的高度差的绝对值之和。小Z希望他劳累值最小,所以他想问这个劳累值最小能是多少?
输入输出格式
输入格式:
第一行两个整数,表示有座山且他准备爬其中恰好座山。
第二行个数,分别给出每座山的高度。
输出格式:
输出一个整数,表示最小的劳累值。
说明
样例中,小Z可以选择爬1.2.3三座山或者2.3.4三座山,劳累值为1+1=2
对于50%的数据,
对于100%的数据,
————————————————我是分割线——————————————————
好吧,我们切入正题,这题很显然就是一道DP题(这不是废话。。)
那么问题就在于我们能否写出O(n^2)或者是O(n^2logn)的DP呢?
如果我们用暴力显然是不行的。
因为我们如果假设f[i][j]表示爬了i座山,最后一座山为j,那么我们显然需要一个循环来枚举i,同样,也要枚举j,
但是由于我们要将DP方程向下推,我们还要枚举k(j+1<=k<=n)
这样就是O(n^3)期望得分50分
可是我们想做出O(n^2)DP的话,几乎是不可能的。
那么我们如果退而求其次,自然就是O(n^2logn),自然想到线段树。
我们可以通过线段树维护最值来优化DP
这道题目中最烦的就是绝对值
因为如果是绝对值就是说明有2种情况,我们无法在一棵线段树上处理。
所以对于f[i][j],我们自然列出DP方程。
f[i][j]=min(min(f[i-1][k]+num[k]-num[j](j<=k<=n)),min(f[i-1][k]-num[k]+num[j](1<=k<=j)))
然后我们就可以用两颗线段树优化min中的2个数啦!
下面贴代码
#include<cstdio> #include<cstring> #define ls k<<1 #define rs k<<1|1 #define M (1<<10) #define MN 2005 #define min(a,b) ((a)<(b)?(a):(b)) #define inf 0x3f3f3f3f using namespace std; int n,m; int qmin[M<<1],qmax[M<<1]; int f[MN][MN],num[MN]; void update(int k,int val1,int val2) { k+=M,qmin[k]=min(qmin[k],val1),qmax[k]=min(qmax[k],val2); for(k>>=1;k;k>>=1)qmin[k]=min(qmin[ls],qmin[rs]),qmax[k]=min(qmax[ls],qmax[rs]); } int queh(int k) { int qaq=inf; for(k+=M-1;k!=1;k>>=1) if(~k&1)qaq=min(qaq,qmax[k^1]); return qaq; } int quel(int k) { int qaq=inf; for(k+=M+1;k!=1;k>>=1) if(k&1)qaq=min(qaq,qmin[k^1]); return qaq; } int main(){ memset(qmin,0x3f,sizeof(qmin)); memset(qmax,0x3f,sizeof(qmax)); memset(f,0x3f,sizeof(f)); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)scanf("%d",&num[i]),f[1][i]=0; for(int i=2;i<=m;i++) { for(int j=1;j<i;j++)update(num[j],f[i-1][j]-num[j],f[i-1][j]+num[j]); for(int j=i;j<=n;j++) { f[i][j]=min(queh(num[j])-num[j],quel(num[j])+num[j]); update(num[j],f[i-1][j]-num[j],f[i-1][j]+num[j]); } memset(qmin,0x3f,sizeof(qmin)); memset(qmax,0x3f,sizeof(qmax)); } int ans=inf; for(int i=m;i<=n;i++) ans=min(ans,f[m][i]); printf("%d ",ans); }