关于西电ACM校赛D题的思考分析

Posted akyna-zh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于西电ACM校赛D题的思考分析相关的知识,希望对你有一定的参考价值。

Problem D 咕的头发问题

时间限制:1 秒
空间限制:512 MB

题目描述(fixed后的)

咕咕是一只神奇的咕咕,他初始拥有的头发数量是无穷大。他研究了一下头发的长势,预测了 n 天内每天头发的净增长,咕咕想让头发茂盛一点,他买了两瓶神奇药水,在药水覆盖时间内净增长 * 2,对于每瓶药水,可以作用于连续的若干天且药水的效果不叠加,他想知道自己最多能增长多少头发,于是他找来了你希望你替他解决。(药水不必全部使用)

输入
第一行一个整数 n,代表天数;接下里一行有 n 个整数,第 i 个数代表第 i天头发的净增长数量。

输出
输出一个整数为最多净增长多少头发。

样例输入
3
1000 -100 1000

样例输出
3900

数据范围
1 < n ≤ 1000000,−1000 ≤ 净增长 ≤ 1000。

思考与分析:

大致题意:

咕咕预测了未来若干天每天的头发净增长数目,有两瓶可以让接下来若干天头发净增长翻倍的药水,只要不洗去药水,药水作用就会一直持续下去。而我们不需要用完药水,或者根本不用也可以。现在我们要求出咕咕在接下来的若干天最大净增长头发数。

数据结构:

这里是关于区间的问题,可以考虑采用一个结构体数组,结构体包含左边界l,有边界r,和以该点为终点的最大子区间和sum。(这个是为了后面动态规划考虑的)

算法分析:

1.首先,我们知道不管加不加药水,本该净增长的头发是一定会长的,所以这部分一定要加上去。

2.然后,我们分析怎么加第一次药水,问题可以简化为在哪一个区间进行翻倍操作,所以必须找到区间和最大的子区间,这里可以用一个动态规划实现。(当然,如果最大和小于等于0,则证明无需使用药水,那么可以直接return了)接着,我们必须想想在最大区间和等大时应该选取范围大的区间还是小的。这一步先缓缓。

关于动态规划:

(1)初始状态:

d[1].l = d[1].r = 1;
d[1].sum = a[1];

(2)转移方程:

d[i] = max(d[i-1].sum + a[i], a[i]);

(3) 具体实现:

	d[1].l = d[1].r = 1;
	d[1].sum = a[1];
	for(int i = 2; i <= n; i++){
		if(d[i-1].sum + a[i] <= a[i]){
			d[i].sum = a[i];
			d[i].l = d[i].r = i;
		}else{
			d[i].sum = d[i-1].sum + a[i];
			d[i].l = d[i-1].l;
			d[i].r = i;
		}
	}
	sort(d+1, d+1+n, cmp);
	if(d[1].sum > 0)	hair += d[1].sum;

(其中a[]数组存的是每天的头发净增长量)

3.再考虑第二瓶药水,当然,这一瓶可加可不加。可以进行讨论:

(1)把它加在第二大子区间内,可以直接由上面sort后的结果得到,这里注意有交集的区间需要先把交集部分去掉再计算。由于动态规划的方式,有交集的区间左边界等于最大子区间左边界。绝对值记为hair1。

p1:先找出与最大区间完全无交集且最大的子区间,和记为hair1:

	for(int i = 2; i <= n; i++){
		if(d[i].l > d[1].r || d[i].r < d[1].l){
			if(d[i].sum > 0){
				hair1 = d[i].sum;
				break;
			}
		}
	}

p2:再找出有交集的部分

	int l = d[1].l, r = d[1].r;
	int mmax = -1000000001;
	int r_max = r;
	for(int i = 2; i <= n; i++){
		if(d[i].l == l && d[i].r > r_max)
			r_max = d[i].r;
	}
	d[r+1].l = d[r+1].r = r+1;
	d[r+1].sum = a[r+1];

再对不属于最大子区间的部分进行动态规划,若sum大于hair1则修改hair1:

	for(int i = r+1+1; i <= r_max; i++){
		if(a[i] + d[i-1].sum < a[i]){
			d[i].sum = a[i];
		}else{
			d[i].sum = a[i] + d[i-1].sum;
		}
		if(d[i].sum > hair1)	hair1 = d[i].sum;
	}

(2)把它加上面最大子区间内,也就是找出一个该区间内负最大子区间,在该区间前洗去药水,再该区间后加上药水。这里在子区间内进行动态规划。绝对值记为hair2。

	d[l].l = l;
	d[l].r = l;
	d[l].sum = a[l];
	int mmin = 1000000001;
	for(int i = l+1; i <= r; i++){
		if(d[i-1].sum + a[i] <= a[i]){
			d[i].sum = d[i-1].sum + a[i];
		}else{
			d[i].sum = a[i];
		}
		if(d[i].sum < 0 && d[i].sum < mmin)	mmin = d[i].sum;
	}
	if(mmin < 0)	hair2 = -mmin;

比较hair1与hair2,选择大的一个就好了。当然我们是把hair1和hair2初始化为0的,如果hair1和hair2都为0,证明无需使用第二瓶药水。

4.那么现在返回第2点,我们应该要选范围大的还是小的呢?通过第三点的分析,不难发现应该选择范围小的,因为我们是要在最大区间内寻找负最大区间,在最大区间外寻找正最大区间,显然必须让尽可能多的正值位于最大区间外面。这样就可以编写我们的sort函数了。

bool cmp(D d1, D d2){
	if(d1.sum > d2.sum)	return true;
	if(d1.sum == d2.sum)	return d1.r - d1.l <= d2.r - d2.l;
	if(d1.sum < d2.sum)	return false;
}

5.结束:

hair += max(hair1, hair2);
cout << hair;

后记:

刚开始看这题的时候感觉无从下手,要考虑的东西感觉挺多,但是其实仔细思考分析后,感觉这个题也是不算难的。如果懂点动态规划,见多了这种题,应该就能很快解决,说白了其实还是一道模板题,个人感觉。

over

以上是关于关于西电ACM校赛D题的思考分析的主要内容,如果未能解决你的问题,请参考以下文章

2021西电校赛网络选拔赛 D.咕的头发问题 (dp)

2018ACM校赛 D 白狼(暴力枚举)

郑州轻院ACM校赛骗钱记

2021-10-24第一次ACM校赛_记录

关于一道JS面试题的思考

记校赛