二分算法之派与月度开销

本文通过实例介绍了如何使用二分法解决两类经典问题:分配派和规划月度开销。首先建立二分框架,然后根据问题特性设计isAnswer函数判断条件。对于分派问题,目标是最大化每个人分到的派的体积;对于月度开销,目标是最小化最大开销周期。二分法过程中,根据能否满足条件调整搜索区间,最终找到最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

分派与月度开销是两种经典的二分题型,前者是让最小值尽可能大,后者是使最大值尽可能小。

在求解时,不妨先搭建出二分求解的框架(while循环,left=初值,right=初值,mid=left+(right-left)/2),再对当前mid进行判断是否合适,这时候需要设计一个函数 isAnswer(mid),该函数内部通常需要一个值cnt来计数(分得派的块数,划分的月数),再将该cnt与问题给定的某个值来比较(人数,周期数)。根据比较结果,这个函数会告诉我们当前mid是否能满足最基本的要求,再采取合适的区间调整策略

如果能满足要求,则先保留该mid值。若是最小值尽量大的问题,则要到值更大的区间去求解。调整left=mid(或left=mid+x,x为合适的数,视具体情况而定);若是求最大值尽量小的问题,则要到值更小的区间去求解。则调整right=mid(或right=mid-x,x为合适的数,视具体情况而定)。
如果不能满足要求,则和满足要求时采取相反的区间调整策略。

以下,按照我们的策略分别求解题述两个问题。

一、派

1.题目简述

我有N个不同口味、不同大小的派。有F个朋友会来参加我的派对,每个人会拿到一块派(必须一个派的一块,不能由几个派的小块拼成;可以是一整个派)。

所有人拿到的派是同样大小*的(但不需要是同样形状的),我也要给自己留一块,而这一块也要和其他人的同样大小。

请问我们每个人能拿到的最大派是多少?每个派都是一个高为1,半径不等的圆柱体。

输入
第一行包含两个正整数N和F,1 ≤ N, F ≤ 10 000,表示派的数量和朋友的数量。
第二行包含N个1到10000之间的整数,表示每个派的半径。

输出
输出每个人能得到的最大的派的体积,精确到小数点后三位。

样例输入
3 3
4 3 3
样例输出
25.133

2.问题分析

要求将N个派分给F+1个人(千万别忘了自己),求每个人能分到的最大派的面积(体积)。在每个人都有一块派的前提下,尽量使派的面积大,限定条件就是分的派的块数要大于人数,典型的二分求最大值的问题。

假设每个人分的派大小为area:
(1)如果按照该area能分得至少F+1块派,则保留该area值,同时尝试更大的area。令min=area,在更大的区间值里进行计算;
(2)如果按照该area分得派数少于F+1,则说明area选大了,蛋糕总面积不够,需要尝试更小的area。令max=area,在更小的区间值里进行计算。

此外,需要一个函数 isAnswer() 来判断当前area是否能够满足分派要求

bool isAnswer(double area)
{
   
	int cnt = 0;  //蛋糕能分的的总块数
	for (int cur = 0; cur < N; cur++)
	{
   
		cnt += floor(X[cur] / area);
	}
	//蛋糕的总块数不少于朋友+自己的数量
	return cnt >= F+1;
}

还需注意的是,题目要求保留三位小数,但是二分的精度不能设为10-3或者10-4,否则会导致还能继续增大area值的时候提前退出求解。要注意区分计算精度和保留小数的区别

3.源代码

// pai.cpp 

#include <iostream>
#include <algorithm>
#include <vector>
#include <iomanip>
#include <cmath>
using namespace std;

const double pi = acos
### 使用Python实现月度开销统计 为了有效地管理和优化约翰的月度开销,可以采用二分查找算法来解决这个问题。通过设定合理的上下限并逐步缩小范围,最终找到满足条件的最大月度开销。 #### 定义问题边界 对于给定的日支出列表`days_expenses`以及要划分成的fajo月数量`M`: - **最大值(max)**:如果所有的日子都放在同一个fajo月中,则此fajo月的总费用即为所有日子里最高可能的单个月份开支。 - **最小值(min)**:假设每个月都是独立的一个fajo月,那么最高的那个单独月份的成本将是最低界限。 ```python def find_max_daily_spend(days_expenses): """找出每日花销中的最大值""" return max(days_expenses) def sum_all_days_spends(days_expenses): """计算所有天数内的总花费""" return sum(days_expenses) ``` #### 实现二分法寻找最优解 基于上述定义,在已知最坏情况下的最大月度开销(即全部加起来)和最好情况下最大月度开销的基础上应用二分搜索技术。 ```python from typing import List def can_partition_with_limit(limit, days_expenses:List[int], m:int)->bool: count = 1 current_sum = 0 for expense in days_expenses: if current_sum + expense > limit: count += 1 current_sum = expense if count > m: return False else: current_sum += expense return True def min_max_monthly_cost(days_expenses:List[int], m:int)->int: low = find_max_daily_spend(days_expenses)[^3] high = sum_all_days_spends(days_expenses)[^2] while low < high: mid = (low + high) // 2 if not can_partition_with_limit(mid, days_expenses, m): low = mid + 1 else: high = mid return low ``` 这段代码实现了对输入参数`days_expenses`(表示每一天的具体消费数额)`m`(代表希望分成多少个fajo月),并通过调用辅助函数`can_partition_with_limit()`判断当前尝试设置的最大允许月度成本能否让这些天被成功划分为不超过指定数目(`m`)个fajo月。最后返回的结果就是能够使任意一个fajo月内发生的最大开销达到最小化的数值[^2]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值