二分查找---月度开销

来源:
题目描述:
农夫约翰是一个精明的会计师。他意识到自己可能没有足够的钱来维持农场的运转了。他计算出并记录下了接下来 N (1 ≤ N ≤ 100,000) 天里每天需要的开销。
约翰打算为连续的 M (1 ≤ M N ) 个财政周期创建预算案,他把一个财政周期命名为fajo月。每个fajo月包含一天或连续的多天,每天被恰好包含在一个fajo月里。
约翰的目标是合理安排每个fajo月包含的天数,使得开销最多的fajo月的开销尽 可能少
题目分析:
1.二分查找的核心是找到一个 有序的序列 ,然后 折半查找 。很显然,题目中给定的N天的开销序列,不能进行排序否则无法把它们进行结组,进行生成一个月度。
2.那么有序的序列在哪里?
思路1:
很显然,这M个月度最小的开销就是单天开销的最大值max(N),但是这个值可能不能使这里序列结成M个月。接下来就是让扩大这个基准,及令max(N)和临近的X相加生成新的值,然后重新进行测试。 这时整个“扩基”过程就演变成了枚举,时间复杂度是o(n),显然会让程序超时:思路1不可行。
思路2:
这是找到一个更好的广义的有序序列,
X0,X1,X2,...,XN
Y1 = X0+X1,Y2=Y1+X2, YN = YN-1+XN, 很显然Y是有序的;但是,我们并不需要这个细粒度的序列,只需去{N}中的最大值和sum(N)分别作为控制的左右端点即可。
while(L<=R)
{
D = (L+R)
核心操作1;序列N经过划分、结组后是否能满足M的规定
核心操作2:根据N的划分情况进行边界控制
}
核心操作1:
这里需要判断这时的序列在最大开销为D时,是否能满足M的限制,即
1.从左向右开始划分序列N;2.判断生成的月度 总数和M的关系
划分方法1: 划分方法2;
sum_temp = exp[0]; sum_temp = 0;
total = 0; total = 1; 最后一项exp[N-1]没法处理
for(int i = 1;i<N;i++) for(int i = 0;i<N;i++)
{ {
sum_temp+=exp[i]; if(sum_temp+exp[i]>D)
if(sum_temp>D) {
{ sum_temp = exp[i];//重新开始计算
total++; total++; //月度
sum_temp = 0; }
i--; else
} sum_temp+=exp[i];
else if(sum_temp==D) }
{
total++;
sum_temp = 0;
}
else (最后的一项需要独立算成一个month)
{
if(i==N-1)
total++;
}
}
两种方法一个很大的区别是total的初始值,方法一从零开始计算,而方法二从一开始,其原因在于两个方法是否对最后一个月度进行统计。以方法二为例,假设最后两天是400、500,当后sum_temp = exp[N-1]就无法进入循环体,这时的sum_temp显然是一个新的月度,但total并没有自增;假设最后两天是100、300,他们也是一个月度,但是计数total也没有统计他们。
综上可知,方法二没有统计最后一个月度,所以total就从1开始计算。
方法1对i==N-1进行了控制,所有从total=0开始统计也是可以的。
核心操作二: (边界条件设置,一定要仔细,不要想当然)
通过统计后total有三种状态,total==M ,total>M, total<M。其中,其中total==M说明对N的划分满足条件, 可以进一步划分 。如果total>M说明划分的组数过多,即D设置的过小,需要调整左边界,在D值更大的范围内搜索;如果total<M则说明划分的组数过少,即上限D设置的过大,需要调整右边界,在D值更小的范围内搜索。
if(total>M)
L = D+1;
else
R = D-1;
参考答案:
#include<math.h>
#include<iostream>
using namespace std;
int main()
{
int N,M;
int exp[100010];
int max_exp = -1,sum = 0;
cin>>N;
cin>>M;
for(int i = 0;i<N;i++)
{
cin>>exp[i];
max_exp = max(max_exp,exp[i]);
sum+=exp[i];
}
int L = max_exp;
int R = sum;
//cout<<L<<" "<<sum<<endl;
int D,sum_temp,total,last_D=0;
while(L<=R)
{
D = (R+L)/2;
//D=500;
sum_temp = exp[0];
total = 0;
bool flag = false;
for(int i = 1;i<N;i++)
{
sum_temp+=exp[i];
if(sum_temp>D)
{
total++;
sum_temp = 0;
i--;
}
else if(sum_temp==D)
{
total++;
sum_temp = 0;
}
else
{
if(i==N-1)
total++;
}

}
//cout<<total<<endl;
if(total<=M)
{
last_D = D;
R = D-1;
}
else
L = D+1;
}
cout<<last_D;
}
不做伸手党

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值