OpenJudge 月度开销

目录

 

月度开销:

要求:

描述: 

输入: 

输出: 

样例输入:

样例输出:

提示: 

问题分析: 

最终代码:

总结:


月度开销:

要求:

总时间限制: 1000ms

内存限制: 65536kB

描述: 

农夫约翰是一个精明的会计师。他意识到自己可能没有足够的钱来维持农场的运转了。他计算出并记录下了接下来 N (1 ≤ N ≤ 100,000) 天里每天需要的开销。

约翰打算为连续的M (1 ≤ M ≤ N) 个财政周期创建预算案,他把一个财政周期命名为fajo月。每个fajo月包含一天或连续的多天,每天被恰好包含在一个fajo月里。

约翰的目标是合理安排每个fajo月包含的天数,使得开销最多的fajo月的开销尽可能少。

输入: 

第一行包含两个整数N,M,用单个空格隔开。
接下来N行,每行包含一个1到10000之间的整数,按顺序给出接下来N天里每天的开销。

输出: 

一个整数,即最大月度开销的最小值。

样例输入:

7 5
100
400
300
100
500
101
400

样例输出:

500

提示: 

若约翰将前两天作为一个月,第三、四两天作为一个月,最后三天每天作为一个月,则最大月度开销为500。其他任何分配方案都会比这个值更大。

问题分析: 

我们先来分析一下题意:约翰希望为自己未来N天的开销做一个预算估计,所以他需要将这N天的预算进行M个财政周期的分划,将N天的预算“连续地”分成M个互不相交的集合:\left \{ Cost_{1},Cost_{2}, \cdots Cost_{N} \right \}=\bigcup_{i=1}^{M}Fajo_{i}

记作M个fajo月,并使得这M个Fajo月的预算的最大值最小。我们的目标就是求出这个最小预算。

{\color{Red}\min_{1\leqslant i\leqslant M}\left\{ ||Fajo_{i}||\right\}}

如果你对于上面的题意分析并不是很理解,我们来看一下样例和提示。样例中给出的预算有N=7天,用集合表示的话就是\left\{100,400,300,100,500,101,400 \right\},我们需要把这个集合“连续地”拆成{\color{Red} \left \{ 100,400 \right \}\cup\left\{300,100 \right\}\cup\left\{500\right\}\cup\left\{101\right\}\cup\left\{400\right\}},这样的话,Fajo月中预算最大值为500。假设有比500更小的答案,那么500这个元素始终无法归入任何一个集合中,所以500就是最优的情况。

通过分析样例,我们似乎发现此题很难,连样例都要简单证明一下才能给出解答,那么这个问题应该怎么来解决?当我们的思维似乎无法对问题直接给出一个完备解法时,我们就要依靠计算机的强大计算能力,通过搜索枚举来解决问题。

但是这一想法又再次“碰壁”。题中说N的范围是1~100000,这个数据过于之大,除了O(1)和O(n)的复杂度以外,基本上都是超时的结果了。如果我们对所有情况进行遍历的话,最差情况是需要C_{N}^{\frac{N}{2}}次才能算完,这是极其恐怖的次数,不可接受。所以我们有必要对搜索进行适当的优化(二分)

和N最大为100000相比,每日预算最多10000就显得小了许多。我们知道答案一定在MaxCost与SumCost之间。(MaxCost:单日最大预算,SumCost:所有预算总和)所以我们可以不去搜索集合分划的情况,我们可以直接搜索答案,如果答案直接匹配,输出就可以了。但是直接搜索答案也不能盲目搜索,我们需要“反复横跳”,利用类似二分法寻找方程近似解的方法一步步缩小范围,直到我们最终求出了目标解(范围锁定到一个数上)。对于搜索过程中的节点验证一下是否可将原数据分为M组。如果FajoCount>M,说明当前值过小,如果FajoCount<=M,说明当前值可能偏大。

最终代码:

#include<iostream>
using namespace std;
int main(){
    int N;//总天数
    int M;//总fajo月数
    scanf("%d%d",&N,&M);
    int *Cost=new int[N];
    int MaxCost=0;int SumCost=0;
    for(int i=0;i<N;i++){
        scanf("%d",&Cost[i]);
        MaxCost=max(MaxCost,Cost[i]);
        SumCost+=Cost[i];
    }
    int Left=MaxCost;
    int Right=SumCost;
    int Middle=Left+(Right-Left)/2;
    while(Left!=Right) {
        int FajoCount = 1;
        int TempSum = 0;
        for (int i = 0; i < N; i++) {
            TempSum+=Cost[i];
            if (TempSum > Middle) {//如果超过了Middle,说明不含该点之前的点组成一个Fajo月
                FajoCount++;
                TempSum=Cost[i];
            }
            else if(TempSum==Middle){//如果刚好等于Middle,说明含该点组成一个Fajo月
                FajoCount++;
                TempSum=0;
            }
        }
        if(FajoCount<=M){
            Right=Middle-1;
        }
        else{
            Left =Middle+1;
        }
        Middle=Left+(Right-Left)/2;
    }
    printf("%d\n",Middle);
    delete []Cost;
    return 0;
}

总结:

二分对于搜索的优化是十分显著的,一定要善用二分法。(光是输入就已经O(n)了,所以多余操作很容易超时)希望这篇文章可以为您带来帮助。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值