每日一题,坚持使我强大
今日份快乐:codeforces 448C 传送门
明天份快乐:codeforces 1358D 传送门
题目大意
有 n 块紧挨的栅栏,每块栅栏的高度为 a[i], 宽度为 1。现用一个宽度为 1 的刷子,给这 n 块栅栏刷漆。可以横着刷,也可以竖着刷,可以重复刷,但必须刷在栅栏上。
问:最少要刷多少次。
分析
这个题用分治法解决,先来简单的说一下分治。
分治,字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。在计算机科学中,分治法就是运用分治思想的一种很重要的算法。分治法是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)等等。
这段话来自百度百科,详细解释看这里:传送门
这个题显然,我们如果选择横着刷,我们肯定会先刷到最矮的那块栅栏处,而这时,就会出现断层(如下图),要刷满断层的两侧显然是两个相互独立的子问题,对这两个相互独立的子问题进行刚刚相同的操作,拿肯定会得到更多的子问题,直到一个区间中没有栅栏的时候。
我们定义一个函数 solve(int l, int r, int h),这个函数的作用是,返回刷满从高度 h 开始,从第 l 块栅栏到第 r 块栅栏之间的最少粉刷次数。
函数递归很简单,就是以区间的最小值为界限分成两个新的区间。递归的出口也很简单就是 l > r 时就不用再递归了,这时区间内已经没有了栅栏,说明栅栏已经刷满。下面是函数功能的实现:
对于一个区间,要么全部竖着刷,要么横竖交替的刷,统计这两者的次数,取最小值便好。
全部竖着刷的次数:统计区间内大于 h 的栅栏的个数 sum,这都是要竖着刷的。
横竖交替的刷的次数:如上图,先横着刷 a[ind] - h 次(这个 a[ind] 便是区间内的最小值),再递归 solve(l, ind - 1, a[ind]) 和 solve(ind + 1, r, a[ind]) 去刷满剩下的区间。总次数 res = a[ind] - h + solve(l, ind - 1, a[ind]) + solve(ind + 1, r, a[ind])。
最终的结果 ans = min(sum, res)。
分解子问题的过程是 ’ 分 ',解决子问题的过程是 ’ 治 '。由原问题到子问题,再回归到原问题的整个过程便是 ’ 分治 '。
代码
#include <bits/stdc++.h>
using namespace std;
const ll INF = 2e9;
int a[5005];
int solve(int l, int r, int h){
if(l > r) return 0;
int minn = INF, ind = -1; // 初始化
int sum = 0; // 纪录竖着刷的次数
for(int i = l; i <= r; i++){
if(a[i] < minn) minn = a[i], ind = i; // 找到这个区间中的最小值,为下一次分区做准备
if(a[i] > h) sum++; // 如果这个栅栏的高度大于 h 就可以竖着刷
}
int num = minn - h; // 计算横着刷的次数
int next_h = minn;
int res_l = solve(l, ind - 1, next_h);
int res_r = solve(ind + 1, r, next_h): // num + res_l + res_r 就是横竖交替刷的次数
int res = min(sum, res_l + res_r + num); // 比较是全部竖着刷的次数少还是横竖交互刷的次数少
return res;
}
int main() {
ios::sync_with_stdio(false);
int n;
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
int res = solve(1, n, 0);
cout << res << endl;
return 0;
}
欢迎大佬留言指错,哪里讲的不清楚也欢迎留言提出