蓝桥杯--排水管道--java

问题描述

小蓝正在设计一个排水管道的工程,将水从西边一个离地面很高的水库引到东边的地面。

为了项目更有艺术性,小蓝建造了很多高低不同的的柱子用于支撑流水的水槽,柱子之间的间距相同。

当西边的柱子比东边相邻的柱子至少高 1 时,水能正常通过水槽向东流,否则可能出现异常。

但是,小蓝发现施工方建造的柱子是忽高忽低的,不满足西边高东边低的要求。小蓝不得不修改柱子的高度以便水能正常通过水槽。同时,小蓝需要用 上所有的柱子,最东边的柱子高度至少为 1,最西边的柱子高度可以为任意高度。每修改一根柱子的高度,需要花费 1 的代价(不管高度改变多少代价都是 1)。

请问,小蓝最少花费多大的代价,可以让水能正常通过所有水槽?在修改时,小蓝只会把柱子的高度修改为整数高度。

输入格式

输入的第一行包含一个整数 n,表示柱子的数量。
第二行包含 n 个整数 h1 , h2 , ···, hn,从西向东表示每根柱子的高度。

输出格式

输出一行包含一个整数,表示答案。

样例输入

6
8 9 5 2 1 1

样例输出

3

样例说明

至少修改三根柱子,高度变为 8,7,5,3,2,1。

评测用例规模与约定

对于 30% 的评测用例,2 ≤ n ≤ 100,1 ≤ hi ≤ 100 。 对于 60% 的评测用例,2≤n≤1000,1≤hi​≤10000。 对于所有评测用例,2 ≤ n ≤ 100000,1 ≤ hi ≤ 10^9。

运行限制

最大运行时间:1s
最大运行内存: 256M

解答

这个题目是求下降子序列的最大长度,很容易想到一个O(n2)的动态规划算法

动态规划(70%)

该算法优化了快速输入和求stream流max但复杂度太高不能ac,超时了3个
算法概述:

  1. dp[i]数组代表前面到i所能的最大长度

    如dp[3]=2代表前面4个只有2个可以和这个数组成下降序列
    
  2. 实现dp

    我们实现是从0-n遍历如在第一个的时候,在遍历后面的是否能符合这个位置(能不能组成下降队列)
    在这个位置的话需要满足以下要求 : 这个数大于n-j(因为最左边要大于1)这个数要小于a[i]-(j-i)(每次下降需要少1)
    如果满足则我们将j可以以i为前缀 得到动规 dp[j] = Math.max(dp[j],dp[i]+1)
    
import java.io.*;
import java.util.Arrays;
import java.util.OptionalInt;
import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改

public class Main {
     private final static StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    public static int nextInt() throws IOException {
        st.nextToken();
        return (int) st.nval;
    }
    public static void main(String[] args) throws IOException {
        //在此输入您的代码...
        int n = nextInt();
        int[] a = new int[n];
        for (int i = 0; i < n; i++) {
            a[i] = nextInt();
        }
        int[] dp = new int[n];
        for (int i = 0; i < n-1; i++) {
            for (int j = i+1; j < n; j++) {
                if (a[j] >= n-j && a[j] <= a[i] - j+i)
                    dp[j] = Math.max(dp[j],dp[i]+1);
            }
        }
        OptionalInt max = Arrays.stream(dp).max();
        System.out.println(n-1-max.getAsInt());
    }
}

优化(100%)

我们想要找到一个最大下降序列然后用长度减去最大序列的长度得到答案是我们的思路。
首先从上面的分析中我们有结论

优化一

满足下降序列每一个点的到最后一点的长度想要大于自身(因为最后一个最小为1) 那么自身就是这个数,长度就是n - 下标。 
所以我们可以得到下标+数 >= n 

优化二

我们比较2个数的时候会加上距离差,那么我们干脆用小标加上数来代表这个的贡献。省去了对距离的比较。

然后我们来求得最大下降序列

遍历每个符合要求的数
如果这个数比序列的最小的数小,则加到序列的最后
否则,我们将其代替到,比这个数小的最小索引位置。

最终得到最大下降序列
用长度减去序列则是需要修改的

优化三

用二分法求得最合适位置

为什么是这样的一个位置呢

这个思想类似于动态规划,序列里面的数并不是一定最大下降序列,只是长度一定相等。
举个例子 10 7 6 9 8 7
那么我们开始能够得到10 7 6 这样的序列
到9 的时候 就会变成 10 9 6 但是我们如果不进行修改,后面的可能性就会大大降低,就像6后面最多就5个数字了
然后在进行修改就是10 9 8 ,8后面可能性就更多了
emm表达的好像不是很明确。应该也差不多。
import java.io.*;
import java.util.*;
// 1:无需package
// 2: 类名必须Main, 不可修改

public class Main {
     private final static StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    public static int nextInt() throws IOException {
        st.nextToken();
        return (int) st.nval;
    }
    public static void main(String[] args) throws IOException {
        int n = nextInt();
        ArrayList<Integer> a = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            int j = nextInt();
            if (i + j >= n)
                a.add(j+i);
        }
        LinkedList<Integer> d = new LinkedList<>();
        for (Integer i : a) {
            if (d.isEmpty() || d.peekLast() >= i) {
                d.offer(i);
                ans++;
            }
            else
                d.set(binarySearch(d,i),i);
        }
        System.out.println(n-d.size());
    }

    public static int binarySearch(List<Integer> a, int b) {
        int l = 0, r = a.size() - 1;
        while (l < r) {
            int mid = (l + r) >>> 1;
            if (a.get(mid) < b)
                r = mid;
            else
                l = mid + 1;
        }
        return r;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只小余

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值