POJ 3666 Making the Grade 离散化+DP解法,以及一点个人感悟

题目:http://fastvj.rainng.com/problem/10721/origin

题意:一条笔直的土路连接着FJ农场上的两个田地,但它改变的海拔比FJ想要的要多。他的牛不介意爬上或下一个斜坡,但他们不喜欢交替的山丘和山谷。FJ想要增加和清除道路上的泥土,这样它就变成了一个单调的斜坡(无论是向上倾斜还是向下倾斜)。 给你N个整数A1, ... , AN (1 ≤ N ≤ 2,000) 描述海拔(0 ≤ Ai ≤ 1,000,000,000) 在路上N个等距的位置,从第一个字段开始,到另一个字段结束。FJ想把这些高度调整成一个新的序列B1, . ... , BN 这要么是不增加的,要么是不减少的。由于增加或清除沿路任何位置的污物所需的费用相同,因此修路的总费用为 | A 1 - B 1| + | A 2 - B 2| + ... + | AN - BN | 请计算他的道路分级的最低费用,使它成为一个连续的斜坡。FJ高兴地告诉你,有符号的32位整数肯定可以用来计算答案。(翻译源自vjudge)

 

由于不增数列和不减数列具有对称性,所以就拿不减数列来说明

首先题目数据量是2k,所以支持O(n^2)的算法。

一般看第一眼题目第一个想法是贪心的,就有可能是用动态规划来做(滑稽),对于这道题我的第一个想法是假如某一个点比前面的点低的话那么就将这个点调整到和前面那个点一样高,根据经验+直觉我知道我这个策略肯定有问题,很快我就找到一个反例:

1232222

其实,对于一个点,其操作有增加,不变和减少,这三者都必须考虑,对于写程序来说假如没有什么特别的方法一般都需要遍历其所有的状态然后找出最优的解答。加上这道题2000的数据量就是在很赤裸裸的告诉你来吧遍历完我的全部可能的情况吧(滑稽)。到这里就差不多确定是动态规划来做了,dp[i][j]就表示到编号为i,大小为j的数构建成不减数列所需要的最小费用。

既然是动态规划,而这道题的数据量和数据大小严重不符合,那么就会想到离散化。其实假如使用的是离散化那么就相当于认为对于每一个点,其最终的高度都是题中已经给出过的点的高度,这一点也很符合直觉。其实在这里我也没有证明这个策略的可行性,我只给一个我的大概的想法:按不减数列来说,假如两个点分别是 7,4 ,那么将7降到4就是最好的选择,因为从4往右的点的高度已经不受7影响了,只受4影响,而这是个不减数列,假如将7降到3的话就白白多降了一个单位。(不知道这样理解行不行)

离散化之后就开始考虑如何构建状态转移:

get_id是用于离散化的函数

由于数i可以不变,增加,减小,所以我们将它所有的状态都表示出来,对于一个状态的转移,由于其是不减数列,所以遍历方向从下到上,将其前代<j的最小的值和本次需要的费用相加即是结果。

然后不增的话只需要将遍历方向改成从上到下即可

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
#define ll long long
using namespace std;
const int INF = 0x3f3f3f3f;
vector<ll> V;
const int maxn = 2005;
ll dp[maxn][maxn],num[maxn],dp2[maxn][maxn];
int get_id(ll num) { //离散化
	return lower_bound(V.begin(), V.end(), num) - V.begin();
}
int main() {
	memset(dp, 0, sizeof(dp));
	memset(dp2, 0, sizeof(dp2));
	int n; cin >> n;
	for (int i = 1; i <= n; i++) {
		scanf("%lld", num + i);
		V.push_back(num[i]);
	}
	sort(V.begin(), V.end());  
	V.erase(unique(V.begin(), V.end()), V.end()); 
	int ind ,ori;
	for (int i = 1; i <= n; i++) { 
		ind = get_id(num[i]);
		ll minn = INF;
		for (int j = 0; j < V.size(); j++) { // 增
			minn = min(minn, dp[i-1][j]);
			dp[i][j] = minn + (V[ind] - V[j] > 0 ? V[ind] - V[j] : V[j] - V[ind]);
		}
		minn = INF;
		for (int j = V.size() - 1; j >= 0; j--) { // 减 
			minn = min(minn, dp2[i - 1][j]);
			dp2[i][j] = minn + (V[ind] - V[j] > 0 ? V[ind] - V[j] : V[j] - V[ind]);
		}
	}
	ll res = INF;
	for (int i = 0; i < V.size(); i++) {
		res = min(res, dp[n][i]);
		res = min(res, dp2[n][i]);
	}
	printf("%lld", res);
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值