描述
春春是一名道路工程师,负责铺设一条长度为 n 的道路。
铺设道路的主要工作是填平下陷的地表。整段道路可以看作是 n 块首尾相连的区域,一开始,第 i 块区域下陷的深度为 di 。
春春每天可以选择一段连续区间 [L, R] ,填充这段区间中的每块区域,让其下陷深度减少 1。在选择区间时,需要保证,区间内的每块区域在填充前下陷深度均不为 0 。
春春希望你能帮他设计一种方案,可以在最短的时间内将整段道路的下陷深度都变为 0 。
输入
输入文件包含两行,第一行包含一个整数 n,表示道路的长度。
第二行包含 n 个整数,相邻两数间用一个空格隔开,第 i 个整数为 di 。
输出
输出文件仅包含一个整数,即最少需要多少天才能完成任务。
样例输入 [复制]
6
4 3 2 5 3 5
样例输出 [复制]
9
提示
对于 30% 的数据,1 ≤ ? ≤ 10 ;
对于 70% 的数据,1 ≤ ? ≤ 1000 ;
对于 100% 的数据,1 ≤ ? ≤ 100000 ,0 ≤ di ≤ 10000 。
我估计全NOIP没有人和我一样写并查集的了
大概就是给你一个序列,每次操作可以使一个区间的所有数减1,求使整个序列为0的最少操作次数
考虑到要使花费的天数最少,那么首先肯定要让每一次能铺设的地方尽可能多
手玩一下样例
4 3 2 5 3 5
那么肯定是先直接 [ 1 , 6 ] [1,6] [1,6]铺2天,把第三个就铺成0了,然后整个区间就被分为两个部分,显然每个部分都要分开铺
2 1 0 3 1 5
对于左边 2 1,先整个铺一次,1 0,再对左边铺一次,
对于右边也是一样的,5 3 5—2 0 2—0 0 2—0 0 0
仔细体会一下这个过程
发现其实每次操作其实就是整个区间减去一个区间最小值,然后再递归到每一个被区间最小值分开的区间内去求解
其实这里就已经可以分治去做了
但是考场上没想到,而是做了另外一种做法
考虑到将整个操作顺序反过来一下,就是每一次都先将最大的铺到和两边一样低,然后每次就能铺一个区间了
这样每次操作就变成了把一个相同的区间减一
那么这样问题就变成了该怎么维护这样一个相同的区间
对于相同的区间我们可以用并查集维护,每一个联通块就是一段高度相等的区间
这样每次我们只需要把一段区间和左右两边较大的那个合并就可以了
至于合并的顺序
我们可以发现更大的先铺肯定是对的
因为如果一个位置左右两边都比他大的话,那么显然先把左右两边都铺到和他一样高,然后一起铺整个区间一定更优
那么只需要从最大值到最小值一直做就可以了
这样每次都是把当前这个联通块merge到了一个之后才会枚举到的联通块上
#include<bits/stdc++.h>
using namespace std;
inline int read(){
char ch=getchar();
int res=0;
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))res=(res<<3)+(res<<1)+(ch^48),ch=getchar();
return res;
}
int n,pos[100005],fa[100005];
struct node{
int l,r,h;//l、r:维护一个当前区间的左右端点,每次就可以找左右两边的区间
}a[100005];
inline bool comp(int x,int y){
return a[x].h>a[y].h;
}
inline int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
int ans;
int main(){
n=read();
for(int i=1;i<=n;i++)a[i].h=read(),a[i].l=i,a[i].r=i,fa[i]=i,pos[i]=i;
sort(pos+1,pos+n+1,comp);//从大往小枚举
for(int i=1;i<=n;i++){
int f1=find(a[pos[i]].l-1),f2=find(a[pos[i]].r+1),k=find(pos[i]);//每次找当前区间左右两边的联通块
if(a[f1].h<a[f2].h){//找较高的那个合并
ans+=a[k].h-a[f2].h,fa[k]=f2,a[f2].l=a[k].l;
}
else {
ans+=a[k].h-a[f1].h,fa[k]=f1,a[f1].r=a[k].r;
}
}
cout<<ans;
}