铺设道路(NOIP2018提高组day1)

铺设道路

【问题描述】

春春是一名道路工程师,负责铺设一条长度为 n 的道路。
铺设道路的主要工作是填平下陷的地表。整段道路可以看作是 n 块首尾相连的区
域,一开始,第 i 块区域下陷的深度为 d i d i di
春春每天可以选择一段连续区间 [ L , R ] [L,R] [L,R],填充这段区间中的每块区域,让其下陷深
度减少 1。在选择区间时,需要保证,区间内的每块区域在填充前下陷深度均不为 0 。
春春希望你能帮他设计一种方案,可以在最短的时间内将整段道路的下陷深度都变
为 0 。

【输入格式】

输入文件名为 road.in
输入文件包含两行,第一行包含一个整数 n,表示道路的长度。
第二行包含 n 个整数,相邻两数间用一个空格隔开,第 i 个整数为 d i 。

【输出格式】

输出文件名为 road.out 。
输出文件仅包含一个整数,即最少需要多少天才能完成任务。

【输入输出样例 1】

road.in road.out

6
4 3 2 5 3 5
9

见选手目录下的 road/road1.in 和 road/road1.ans 。

【样例解释】

一种可行的最佳方案是,依次选择:
[1,6]、[1,6]、[1,2]、[1,1]、[4,6]、[4,4]、[4,4]、[6,6]、[6,6]。

【输入输出样例 2】

见选手目录下的 road/road2.in 和 road/road2.ans 。

【数据规模与约定】

3;
70% 的数据,1 ≤ ? ≤ 10;
对于 100% 的数据,1 ≤ ? ≤ 100000 ,0 ≤ d i ≤ 10000 。

【解题思路】

思路一:贪心思想

从左到右依次遍历数组,为了尽可能快的填充所有的坑,先将第一个坑填掉。如果第二个坑的深度小于等于第一个坑的深度,则在填充第一个坑的时候,第二个坑也一并被填掉了;如果第三个坑小于等于第二个坑,那么在填充第二个坑的时候,第三个坑也一并被填充掉了……以此类推,只要后面一个坑的深度小于等于前面一个坑的深度,那么就可以跟第一个坑一并填充,否则必须单另填充它。

#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
const int maxn=1e5+5;
int n,a[maxn],ans;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    ans+=a[1];//处理第一个坑
    for(int i=2;i<=n;i++)
        if(a[i]>a[i-1]) ans+=a[i]-a[i-1];//按规则处理后面的
    printf("%d\n",ans);
    return 0;
}

思路二:递归解决

每次我们从当前序列当中选择一个深度最小的坑进行处理,处理完成之后,当前序列有可能当前序列被0分割成若干的子序列,那么继续处理这些子序列,最后全部处理完即可得到答案。
寻找最小值的位置可以使用RMQ算法来优化。

优化前代码
#include<cstdio>
const int maxn=1e5+5;
int n,a[maxn],ans;
int dg(int l,int r,int d)//[l,r]为填充区间,d为已经填充的天数
{
    if(l>r)
         return 0;
    int p=l;
    for(int i=l+1;i<=r;i++)//找出最小值的坑编号p
    {
        if(a[i]<a[p])
            p=i;
    }
    ans+=a[p]-d;
    dg(l,p-1,a[p]);
    dg(p+1,r,a[p]);
    return ans;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    printf("%d\n",dg(1,n,0));
    return 0;
}
优化后代码
#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
const int maxn=1e5+5;
int n,a[maxn],ans,dp[maxn][20];
void ST(int n)//预处理
{
    for (int i = 1; i <= n; i++)
        dp[i][0] = i;
    for (int j = 1; (1 << j) <= n; j++) {
        for (int i = 1; i + (1 << j) - 1 <= n; i++) {
                int ls=dp[i][j-1],lr=dp[i+(1<<j-1)][j-1];
                if(a[lr]>a[ls]) dp[i][j]=ls;
                else dp[i][j]=lr;
        }
    }
}
int RMQ(int l, int r)
{
    int k = log(r-l+1) / log(2);//确定区间长度2^(2k),k为幂
    int lr=dp[l][k],ls=dp[r - (1 << k) + 1][k];
    if(a[lr]>a[ls]) return ls;
    else return lr;
}
int dg(int l,int r,int d)
{
    if(l>r)
         return 0;
    int p=RMQ(l,r);
    ans+=a[p]-d;
    dg(l,p-1,a[p]);
    dg(p+1,r,a[p]);
    return ans;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    ST(n);
    printf("%d\n",dg(1,n,0));
    return 0;
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值