关于西电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题的思考分析的主要内容,如果未能解决你的问题,请参考以下文章