森林的最大美丽值(二分+差分数组)
题目分析
求最小值的最大值,联想到二分。
第一阶段二段性分析
对于所有树的高度都可以大于等于mid,那么我们可以确定高度小于mid的值一定也可以,但是此时我需要找的是最大的高度,那么mid一定比小于mid的值更大,所以小于mid的值我就不用管了,也就是我可以确定我能够舍弃掉mid左边的值。我还想要确定比mid更大的边长是否也满足条件,所以我要在mid的右边继续二分。
if (check(mid)) {l = mid;} //因为mid是符合条件的,所以我要留着它,而不是l=mid+1
对于所有树的高度不能满足都大于等于mid,那么我们可以确定高度大于mid的值一定也不满足,所以大于等于mid的值我就不用管了,也就是我可以确定我能够舍弃掉mid右边的值。我还想要寻找比mid更小的高度是否能满足条件,所以我要在mid的左边继续二分。
else { r = mid - 1; }//因为mid是不符合条件的,所以我不要留着它,而不是r=mid
//主要这里出现了减法,那么求mid那么应该是(l+r+1)/2
综上该题满足二段性,可以用二分,二分的板子就不说了,接下来说一下check函数如何写。
第二阶段写check函数
check(u)要实现的作用是检查能否在m天内让所有树的高度都大于等于u。
依次遍历每一棵树,如果当前树的高度a[i]小于mid,那么需要给他增高,增高耗费的天数是(mid-a[i])。但是我们要注意一旦选择增高不是只给一颗树增高,而是给一个区间里的树进行增高,那么这就涉及到区间修改,我们可以使用差分数组。现在重新考虑遍历每一棵树,当前树的实际高度是a[i]+sum[i],这里的sum[i]是差分数组的前缀和,如果a[i]+sum[i]<x,那么我们要给这棵树增高,耗费的天数和增加的高度是x-a[i]-sum[i],对于差分数组的变化是d[i]+=x-a[i]-sum[i],d[i+k]-=x-a[i]-sum[i],注意这里的i+k可能会有下标越界问题,所以要改成d[min(i+k,n+1)]-=x-a[i]-sum[i],如果耗费的总天数超过了m,即cnt<0
,就返回false,否则返回true。注意这里更改了d[i]后,对应的sum[i]也要随之更新,否则这里d[i]的改变没有传递下去。
关于sum和d数组,用一个实例说一下,假设一开始的值是2 3 4 2 2,每次都会让连续的4个树增高,x是3,那么开始遍历a[1]=2
sum[1] =sum[0]+d[1]=0;
if(a[1]+0<3) 所以d[1]+=3-2-0=1;
d[min(1+4,6)]+=0+2-3=-1;—>d[5]=-1
sum[1]+=3-2-0=1;
a[2]=3
sum[2] =sum[1]+d[2]=1;
a[3]=4
sum[3] =sum[2]+d[3]=1;
a[4]=2
sum[4] =sum[3]+d[4]=1;
if(a[4]+1<3) 不成立
a[5]=2
sum[5] =sum[4]+d[5]=0;
if(a[5]+0<3) 成立
public static boolean check(int x) {
int d[] = new int[100010];
int sum[] = new int[100010];
int cnt = m;
for(int i=1;i<=n;i++) {
sum[i] =sum[i-1]+d[i];
if(a[i]+sum[i]<x) {
cnt-=x-a[i]-sum[i];
d[i]+=x-a[i]-sum[i];
d[Math.min(i+k,n+1)]+=sum[i]+a[i]-x;
sum[i]+=x-a[i]-sum[i];
if(cnt<0) return false;
}
}
return true;
}
第三步二分范围确定
那么这里的高度的最小值是1,最大值就是树的最大高度,原始的最大高度是1e9,假设只有一棵树,每一天都在长高,最多长1e5天,因此树的最大高度为1e9+1e5。
题目代码
import java.util.Scanner;
public class Main {
static int n,m,k;
static int a[] = new int[100010];
public static boolean check(int x) {
int d[] = new int[100010];
int sum[] = new int[100010];
int cnt = m;
for(int i=1;i<=n;i++) {
sum[i] =sum[i-1]+d[i];
if(a[i]+sum[i]<x) {
cnt-=x-a[i]-sum[i];
d[i]+=x-a[i]-sum[i];
d[Math.min(i+k,n+1)]-=x-a[i]-sum[i];
sum[i]+=x-a[i]-sum[i];
if(cnt<0) return false;
}
}
return true;
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
n=scan.nextInt();
m=scan.nextInt();
k=scan.nextInt();
for(int i=1;i<=n;i++) a[i]=scan.nextInt();
int l=1,r=1000000000+100000;
while(l<r) {
int mid=(l+r+1)/2;
if(check(mid)) l=mid;
else r=mid-1;
}
System.out.print(l);
}
}
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 100010;
int n, m, k;
int a[N];
int d[N], sum[N];
bool check(int x) {
for (int i = 1; i <= n; ++i) d[i]=0,sum[i]=0;
int cnt = m;
for (int i = 1; i <= n; ++i) {
sum[i] = sum[i - 1] + d[i];
if (a[i] + sum[i] < x) {
cnt -= x - a[i] - sum[i];
d[i] += x - a[i] - sum[i];
if (i + k <= n) d[i + k] -= x - a[i] - sum[i];
sum[i] += x - a[i] - sum[i];
if (cnt < 0) return false;
}
}
return true;
}
int main() {
cin >> n >> m >> k;
for (int i = 1; i <= n; ++i) cin >> a[i];
int l = 1, r = 1000000000+100000;
while (l < r) {
int mid = (l + r + 1) / 2;
if (check(mid)) l = mid;
else r = mid - 1;
}
cout << l << endl;
return 0;
}
def check(x):
d = [0] * (n + 1)
sum = [0] * (n + 1)
cnt = m
for i in range(1, n + 1):
sum[i] = sum[i - 1] + d[i]
if a[i] + sum[i] < x:
cnt -= x - a[i] - sum[i]
d[i] += x - a[i] - sum[i]
if i + k <= n:
d[i + k] -= x - a[i] - sum[i]
sum[i] += x - a[i] - sum[i]
if cnt < 0:
return False
return True
n, m, k = map(int, input().split())
a = [0] + list(map(int, input().split())) # Using 1-based indexing
l, r = 1, 1000000000+100000#+1e5
while l < r:
mid = (l + r + 1) // 2
if check(mid):
l = mid
else:
r = mid - 1
print(l)