当前位置: 代码迷 >> 综合 >> Making the Grade(POJ-3666)(DP求解,优化DP)
  详细解决方案

Making the Grade(POJ-3666)(DP求解,优化DP)

热度:88   发布时间:2023-11-19 10:31:29.0

  • 前言
  • 题目
  • 思路
  • 代码
  • 题外话

前言

这道题我一开始用最长上升子序列做,结果WA了…但其实差不多,就是求解时要变一下.

题目

传送门
题目大意:
在一条土路上有n段(1<=n<=2000),每段都有一个高度Ai(1<=i<=n)Ai(1<=i<=n),现在你可以对每一段增加或减少一些土,使得这条路变成单调不上升或单调不下降,求改动土的最小值?

思路

首先,这道题数据有Bug,只需要求单调不下降序列就可以
好了,我们来看看怎么写这道题的DP?
我们就假设要凑不下降序列,那么对于一个位置i来说的话只有两点要考虑:
1.将i连着前面变成不下降的土的花费尽量少。
2.i的高度越低越好。
那我们可以用一个数组b来存储所有出现的数,这里是去重、从小到大排了序的
我们的目的就是对于i来说枚举所有b中元素,找到它对应的花费,那这样就可以进行DP了.
我们定义:f[i][j]:i,ib[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]=minf[i?1][k]+abs(a[i]?b[j])(1<=i<=n1<=k<=j<=m)
但我们发现这样写过后是O(n3)O(n3)的时间复杂度对于数据是过不了的,那我们要优化!
我们用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]=Min+abs(a[i]?b[j])(1<=i<=n1<=j<=m)
那就可以过了~~
输出答案时注意不是直接输出f[n][m]f[n][m],而是输出minf[n][i](1<=i<=m)minf[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数组正序的不上升序列的最小花费

  相关解决方案