蓝桥杯----排水管道
问题描述
小蓝正在设计一个排水管道的工程,将水从西边一个离地面很高的水库引到东边的地面为了项目更有艺术性,小蓝建造了很多高低不同的的柱子用于支撑流水的水槽,柱子之间的间距相同。
当西边的柱子比东边相邻的柱子至少高1时,水能正常通过水槽向东流,否则可能出现异常。
但是,小蓝发现施工方建造的柱子是忽高忽低的,不满足西边高东边低的要求。小蓝不得不
修改柱子的高度以便水能正常通过水槽。同时,小蓝需要用上所有的柱子,最东边的柱子高度至少为1,最西边的柱子高度可为任意高度。每修改一根柱子的高度,需要花费1的代价(不管高度改变多少代价都是1)。
请问,小蓝最少花费多大的代价,可以让水能正常通过所有水槽?在修改时,小蓝只会把柱子的高度修改为整数高度。
输入格式
输入的第一行包含一个整数n,表示柱子的数量。
第二行包含n个整数h1,h2,...,hn,从西向东表示每根柱子的高度。
输出格式
输出一行包含一个整数,表示答案。
样例输入
6
8 9 5 2 1 1
样例输出
3
样例说明
至少修改三根柱子,高度变为8,7,5,3,2,1
分析
对这道题的理解:给定一个n个数的正整数序列,你可以修改其中任意的数,但修改后的数仍为正整数序列,请求出
该序列的最小修改个数,使其成为降序排列的序列。
输入:
6
8 9 5 2 1 1
输出:
3
学过动态规划后都应该理解到动态规划中很经典的题目 最长递增子序列,这道题就是动态规划中最长递增子序列题型的变形。对本题我们可以做以下处理:
将序列进行逆序排列,如8 9 5 2 1 1 逆序之后变为 1 1 2 5 9 8。题目中要求求出最小修改个数让其变为降序排列的序列。我们可以求出最小修改个数,让其逆序序列成为升序排列的序列。由于题目中要求均为正整数,因此最左边第一个最小为1,第二个最小为2,以此类推,第n个最小为n。我们可以将原本逆序列依次减去对应位置最小的数,可以得到一个新序列 0 -1 -1 1 4 2。对于这个新序列,如果是负数,则原本的数小于这个位置最小的数,一定需要修改。对于非负数,如果满足递增(用到动态规划中最长递增子序列题型),则不需要修改,否则也需要修改,对于本例,0 1 4 满足递增序列,故2需要修改。但是假如有多个序列满足递增序列,到底该用哪一个呢?我们可以换种思路,求出总序列总长,再减去递增序列(非负数序列)总长,得到的差值就是我们需要修改数的个数,这样就不用考虑满足多个递增序列的情况了。比如本题例子中,序列为
0 -1 -1 1 4 2
个数为6,递增序列为
0 1 4
个数为3, 6-3=3,即有3个数需要修改.
所以本题的关键是如何取求最长递增子序列。
为了使读者更加理解最长递增子序列,我换一个题目解答。
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
分析:
确定dp[i]的定义 本题中,正确定义dp数组的含义十分重要。
dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度为什么一定表示 “以nums[i]结尾的最长递增子序” ,因为我们在做递增比较的时候,如果比较 nums[j] 和 nums[i]
的大小,那么两个递增子序列一定分别以nums[j]为结尾 和 nums[i]为结尾, 要不然这个比较就没有意义了,不是尾部元素的比较那么
如何算递增呢。状态转移方程 位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 + 1 的最大值。
所以:if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
注意这里不是要dp[i] 与 dp[j] + 1进行比较,而是我们要取dp[j] + 1的最大值。
dp[i]的初始化 每一个i,对应的dp[i](即最长递增子序列)起始大小至少都是1.
确定遍历顺序 dp[i] 是有0到i-1各个位置的最长递增子序列 推导而来,那么遍历i一定是从前向后遍历。
j其实就是遍历0到i-1,那么是从前到后,还是从后到前遍历都无所谓,只要吧 0 到 i-1 的元素都遍历了就行了。 所以默认习惯
从前向后遍历。
遍历i的循环在外层,遍历j则在内层,代码如下:
for (int i = 1; i < nums.size(); i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
}
if (dp[i] > result) result = dp[i]; // 取长的子序列
}
举例推导dp数组
输入:[0,1,0,3,2],dp数组的变化如下:
分析完最长递增子序列,主要代码如下:
int lengthOfLIS(vector<int>& nums) {
if (nums.size() <= 1) return nums.size();
vector<int> dp(nums.size(), 1);
int result = 0;
for (int i = 1; i < nums.size(); i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
}
if (dp[i] > result) result = dp[i]; // 取长的子序列
}
return result;
}
结合本题,不难写出本题代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
const int INF=1e9+10;
int a[N],b[N],dp[N];
void m(int n){
for(int i=0; i<=n; i++) dp[i]=INF;
}
int main(){
int n; cin>>n;
for(int i=1; i<=n; i++) cin>>a[i];
reverse(a+1,a+1+n);
for(int i=1; i<=n; i++) b[i]=a[i]-i;
int k=1;
while( b[k]<0 ) k++;
for(int i=0; i<=n; i++) dp[i]=INF;
dp[1]=b[k];
int mx=dp[1];
for(int i=k+1; i<=n; i++){
if( b[i]<0 ) continue;
*upper_bound(dp+1,dp+1+n,b[i])=b[i];
}
int ans=0,t=1;
while( dp[t++]!=INF ) ans++;
cout<<n-ans<<endl;
return 0;
}