链接:http://codeforces.com/problemset/problem/460/C
题意:
给定n个树,给m天,长度w。可以浇水m天,每天浇水可以使长度为w的连续的这部分树增高 1 。要求浇水之后,这些树的最小值最大是多少。
思路:
典型的最大化最小值问题。用二分查找这个最小值,并判断这个值能否作为最小值,如果可以更新low,变大这个最小值,不行则更新high。
判断的时候,要改变树的高度,最直接想到的就是遍历这个序列,遇到树的高度比这个最小值小的时候就浇水,并且将以这个树为起点的长度为w的树都浇水。这样的复杂度是
O(n2)
,是会T的。
降低更新的复杂度:
可以考虑用一个标记 c 来记录当前这个树已经浇了多少水,每一次判断这个树的高度加上已经浇的水是否已经达到当前的最小值了,如果没有达到就浇水,并且将这个标记再加上所需要的水,还需要运用一个数组 b[ ]来记录浇水的情况,对于每一个浇水的区间尽头的下一个树,消除浇水的影响,也就是减掉所需要浇水的天数b[i] -= p,那么对于每个树将c += b[i] 更新已经浇了多少水。
记录已经浇的水,并且在浇水的区间外消除之前浇水的影响。减少了更新的操作,将复杂度降低为O(n)。
总结:
这个方法很不错,感觉在集训的期间也见到过几次,应该要学起来。可以运用在区间的成段更新之中。。当然这道题也可运用线段树来做这件事,记录区间最小值,运用懒惰操作来进行。复杂度为
O(n∗logn)
。
但是这个方法明显写起来方便一点,还会更快一点~
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define M 100009
#define INF 0x3f3f3f3f
int n,m,w;
int a[M];
int b[M]; //记录浇水
int c;
bool judge(int x)
{
int k = m;
int c = 0;
memset(b,0,sizeof(b));
for(int i = 0;i < n;i++)
{
c += b[i];
if(x > a[i]+c) //高度不够还需要浇水
{
int p = x -a[i]-c; //所需浇的水
if(p > k) return false; //能够浇水的天数已经不够
k -= p;
c += p;
int minn = min(i+w,n);
b[minn] -= p; //在过了长度w之后,消除浇水的影响
}
}
return true;
}
int main()
{
while(scanf("%d %d %d",&n,&m,&w)==3)
{
int low = INF;
int high = -INF;
for(int i = 0;i < n;i++)
{
scanf("%d",&a[i]);
low = min(low,a[i]-1); //将最小的-1
high = max(high,a[i]+1); //将最大的+1 防止如果最大-最小<=1就无法开始二分
}
high += m;
int mid;
while(high - low > 1) //最大化最小值
{
mid = (low+high)/2;
if(judge(mid)) low = mid;
else high = mid;
}
printf("%d\n",low);
}
return 0;
}