题目链接:大楼间穿梭
题意
给你一个长度为n的序列,你现在处在下标为1的点,你现在有两个操作向右边行走。
- 花费x点体力,移动到右侧k幢建筑中第一栋大于等于当前位置值的点。
- 花费y点体力,向右移动一步或者两步。
问最少花费多少体力才能到达最后一个位置上的点。
题解
本题很明显不存在贪心策略使得答案最优,所以这时就需要优雅的暴力dp来帮助我们解决此类问题了。
本题的状态定义和转移很容易定义。
dp[i]:移动到位置i时最少需要多少体力。
那么转态转移由题意得:
d p [ i ] = m i n ( d p [ i ] , m i n ( d p [ i − 1 ] + y , d p [ i − 2 ] + y ) ) ( 由 操 作 2 得 ) {dp[i]=min(dp[i],min(dp[i-1]+y,dp[i-2]+y)) (由操作2得)} dp[i]=min(dp[i],min(dp[i−1]+y,dp[i−2]+y))(由操作2得)
d p [ f i [ i ] ] = m i n ( d p [ f i [ i ] ] , d p [ i ] + x ) ( ( f i [ i ] − i ) ≥ k ) ( 由 操 作 1 得 ) {dp[fi[i]]=min(dp[fi[i]],dp[i]+x) ((fi[i]-i) ≥k)(由操作1得)} dp[fi[i]]=min(dp[fi[i]],dp[i]+x)((fi[i]−i)≥k)(由操作1得)
( f i [ i ] 是 位 置 i 右 边 第 一 个 大 于 等 于 它 的 下 标 ) {(fi[i]是位置i右边第一个大于等于它的下标)} (fi[i]是位置i右边第一个大于等于它的下标)
很容易现在我们只需要解决fi数组就能把问题解决了。
如何找位置i右边的第一个大于等于它的数。
单调栈!!!
我们可以建立一个单调递增的单调栈。每遇到一个小于栈顶的元素入栈,遇到大于等于栈顶的元素则出栈,出栈的每一个元素的fi[i]就是这个大于栈顶这个元素的下标。
代码
long long dp[100010];
int h[100010],fi[100010];
long long inf = 1e18;
class Solution {
public:
/**
* @param heights: the heights of buildings.
* @param k: the vision.
* @param x: the energy to spend of the first action.
* @param y: the energy to spend of the second action.
* @return: the minimal energy to spend.
*/
long long shuttleInBuildings(vector<int> &heights, int k, int x, int y) {
// write your code here.
stack<int> sta;
while(!sta.empty()) sta.pop();
int n=heights.size();
for(int i=0;i<n;i++) h[i+1]=heights[i];
for(int i=0;i<100010;++i) {
dp[i]=inf; fi[i]=-1; }
for(int i=1;i<=n;i++)
{
while(!sta.empty() && h[sta.top()]<=h[i])
{
fi[sta.top()]=i;
sta.pop();
}
sta.push(i);
}
for(int i=1;i<=n;i++)
{
if(i==1) dp[i]=0;
else dp[i]=min(dp[i],min(dp[i-1]+y, dp[i-2]+y));
if(fi[i]==-1) continue;
if(fi[i]-i<=k) dp[fi[i]]=min(dp[fi[i]],dp[i]+x);
}
return dp[n];
}
};