【每日一题】codeforces 448C (分治)

每日一题,坚持使我强大


今日份快乐: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;
    
}

欢迎大佬留言指错,哪里讲的不清楚也欢迎留言提出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值