码农的泪——二分答案总结

本文详细探讨了二分法在编程中的应用技巧,包括整数二分法的实现细节及注意事项,通过具体题目示例解释如何正确设定搜索区间,确保算法高效准确地找到最优解。

       弄了一上午二分答案,终于弄明白了—— 一点点

       第一点,在C++中,因为做除法是向下取整的,所以为了让每一次计算的mid在所选区间内,一定要将左区间闭上。 

       第二点,既然左边闭上了,那右边闭不闭呢?答案是不确定的。我自己喜欢左闭右开,和STL的习惯一样。我这个人很懒,但是这样也不容易出错。我做的一些题证明,左闭右开是可以解决这些问题的。下面分析都将建立与左闭右开的区间选择上讨论。

        我们为什么要做二分优化呢?第一点,我们要的解答是有序的。第二点,当然是省时间了。《编程挑战》中说,进行100次二分验证,解的误差范围可以在10的-30次方左右。非常NICE。

       那么是什么让我这么头痛呢?那就是整数的二分。

      用两道例题来说一下。

      第一道,poj3104晾衣服。 

      

     
#include
#include
#include
#include
using namespace std;
long long n,k,l,r;
long long c[100100];
 
bool solve(int day)
{
	long long count=0;
	for(int i=1;i<=n;i++)
	 {
	 	if(c[i]<=day) continue;
	 	else
	 	{
	 	   long long temp=(c[i]-day)/(k-1);
		   if(temp*(k-1)0)
		{
			int mid=(l+r)/2;
			if(solve(mid)) r=mid;
			else l=mid+1;
		}
		printf("%lld\n",r);
	}
	return 0;
}
    
     思路很简单,二分晾衣服的时间,每次检验,对于衣服i,它含有c[i]的水,那么让它晾干,如果它的水分<=day(即假设的总用时),那么就不需要熨烫。如果大于它,就需要满足以下关系式:
     设它最少需要熨烫n次,那么有:
                        c[i]-k*n+n<=day
     也就是求满足这个不等式的最小正整数。然而难点不在这里。
    我们看主函数。如果我们的mid可以,就说明范围应该向左挪。那么细节来了:l,r,mid以及输出怎样来考虑?
    对于一个区间[a,b),如果它的mid可以,那么接下来有两种情况,
    1:mid就是最优解。
    2:最优解在mid的左边。
    所以我让r=mid,这样如果mid是最优解,输出r就是了。如果不是,那么就接着挪。
    如果mid不可以呢?那么就让区间向右挪,由于区间左边是闭的,就不需要保留了,l=mid+1就可以了,而r还是前一次可行解
    这样,最后会达到什么情况呢?
    [a,a+1),根据前面的分析,a+1肯定是一个可行解。这时,区间的mid就是a,
    如果a可行,r=a,此时区间里没有数了,a就是最优解,此时r=a,输出r就可以了。
    如果a不可行,那么l=a+1,区间里没有数了,a+1就是可行解,而r一直就是可行解,输出r就可以了。
    所以这是思考的精髓,就是让r始终是接近最优解的可行解。而对最后区间的分析,表明循环的要求是r-l>0
    针对本题,它的坑点是k=1要单独判断,因为上面的不等式,解出n之后,分母是k-1!!!!!!!!!!!!!!!!!!
    还有,要用long long ,因为上式中10^9的级别的数一乘会溢出int
    这道题是最小化最大值,下面看一下最大化最小值。


   poj3258奶牛跳石头兼NOIP2015Day2第一题,我居然一开始要用堆做,然而考完了也没反应过来是不对的。然后就跪了。
   
#include
#include
#include
using namespace std;
int L,n,m;
int stone[50010];

bool solve(int s)
{
	int count=0,last=0,now=1;
	while(now<=n)
	{
		if(stone[now]-stone[last]>=s)
		{
			last=now;
			now++;
		}
		else
		{
			now++;
			count++;
		}
	}
	if(stone[n+1]-stone[last]>=s&&count<=m) return true;
	else return false;
}
int main()
{
	#ifdef ONLINE_JUDGE
	#else
	  freopen("in.txt","r",stdin);
	#endif
	scanf("%d %d %d",&L,&n,&m);
	stone[0]=0;stone[n+1]=L;
	for(int i=1;i<=n;i++)
	  scanf("%d",&stone[i]);
	sort(stone+1,stone+n+1);
	int l=0,r=1000001000;
	while(r-l>0)
	{
		int mid=(l+r)/2;
		if(solve(mid)) l=mid+1;
		else r=mid;
	}
	printf("%d",l-1);
} 

   判断的地方,就要注意终点石头和最后剩下的那块之间的距离也要对就行了。
   重点还是说二分。
   这题我们怎样使用左闭右开区间呢?
   总思路:用l控制接近最优解的可行解。
   对于区间[l,r)
   如果它的mid不可以,那么r=mid,那端不要了,不伤大雅。
   如果它的mid可以,那么l=mid,也可以l=mid+1;
   如果我选择了l=mid,那么最后是什么情况呢? 
   不太好想。假设最后是[l,l+2),它的mid一定是l+1。如果l+1可行,那么l=l+1,形成区间[l+1,l+2),l+2不行,那么l+1就是最优解。如果l+1不可行,那么r=l+1,l就是最优解。所以这种方法的循环判定就是r-l>1
   如果我选择l=mid+1呢,假设最后是[l,l+1),l-1是那个最接近最优解的可行解,l是这个区间的mid,如果mid可行,那么l=l+1,区间里没有数了,现在的l就是最优解。说的有点迷糊。对于区间[5,6),4就是它最接近最优解的可行解,那么如果5可以,那么形成区间[6,6},这个区间的l-1,也就是5,就是我们要的答案。如果mid不行,r=5,形成区间[5,5),它的l-1,即4就是最优解。而我附的代码就是按第二种思路写的。

   
  而第一题之所以没有r=mid-1的写法,因为右侧是开的,左+1,右-1,很容易出现[3,1)的情况,就难以控制了,我一测是WA,不好。

   所以,这种二分答案的思路,大同小异,最主要的掌握核心:控制哪个是最接近最优解的可行解,然后就需要灵活处理,看最后的情况。这样,二分答案就没什么可怕的了,来几道,做几道。




    祝大家学习进步,愿OI永葆青春。



<think>首先,用户的问题是关于二分答案算法的原理和使用场景。用户明确说:“我想查找关于二分答案算法的解释和应用 请问二分答案算法原理及使用场景”。所以,我需要专注于二分答案算法的解释和应用。 从系统级指令中,我需要: - 所有行内数学表达式使用$...$格式。 - 独立公式使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,如[^1]。 - 回答结构清晰,帮助用户逐步解决问题,尽量保证回答真实可靠。 用户提供了三个引用,但这些引用是关于二分查找算法的,而不是直接关于二分答案算法的。引用[1]、[2]、[3]都提到二分查找,但引用[2]的标题是“CSP-J 算法基础 二分查找与二分答案”,所以它可能包含二分答案的内容。引用[3]的标题是“二分查找算法的原理和应用场景”,但用户说“二分答案”,所以我需要区分二分查找和二分答案。 回顾用户的问题:“二分答案算法原理及使用场景”。二分答案是一种算法技术,常用于解决优化问题,其中我们猜测一个答案,然后检查它是否可行,使用二分搜索来高效地找到最优解。 我的回答结构应该清晰。我可以这样组织: 1. **介绍二分答案算法**:解释什么是二分答案算法。 2. **原理**:详细说明算法的工作原理,包括数学表达式。 3. **使用场景**:描述常见的应用场景。 4. **示例**:提供一个简单的代码示例或伪代码来演示。 5. **相关问题**:生成相关问题。 由于用户要求生成相关问题,我需要添加一个“相关问题”部分。 现在,关于引用:用户提供了引用,但它们不是用户真正的需求的一部分。指令说:“上述引用仅供参考,并不是用户真正的需求,请尽可能多的参考用户上一次的问题和你的回答。” 但在这个上下文中,用户没有上一次的问题;这是第一次交互。所以,我应该基于引用中的信息,但聚焦于二分答案。 引用[2]提到“二分答案”,所以我可以在回答中引用它。例如,在解释时,自然地添加引用标识。 关键点: - **二分答案原理**:在优化问题中,我们有一个函数 f(x) 表示某个指标,我们想找到满足某些条件的最小或最大 x。使用二分搜索在可能的答案范围内搜索。例如,检查一个值 mid,如果可行,则调整搜索范围。 数学表达式:例如,对于一个函数 f(x),我们想找到最小的 x 使得 f(x) >= k。那么,我们可以定义左边界 L 和右边界 R,然后 while L < R,计算 mid = (L + R) / 2,如果 f(mid) >= k,则 R = mid,否则 L = mid + 1。最后,L 就是答案。 - **使用场景**:常见于问题如“最小化最大值”或“最大化最小值”,例如在分配问题、调度问题、资源分配等。具体例子:在背包问题中找最大价值、在数组中找最小缺失数、在图中找最短路径的最大边权等。 现在,回答结构: 1. **引言**:简要介绍二分答案算法。 2. **原理详解**:解释步骤,包括数学公式。 3. **应用场景**:列出几个常见场景。 4. **代码示例**:提供简单的代码(如Python)来演示。 5. **相关问题**:生成相关问题。 在代码部分,使用代码块格式。 最后,生成相关问题:例如,关于优化、变体等。 确保语言为中文。 添加引用:例如,在提到原理时,如果基于引用[2],可以加[^2]。 引用[2]的标题是“CSP-J 算法基础 二分查找与二分答案”,所以我可以引用它。 现在,草拟回答。</think>### 二分答案算法原理及使用场景详解 二分答案算法是一种高效的优化技术,常用于解决“最小值最大化”或“最大值最小化”类问题。它基于二分查找的思想,通过不断缩小答案的可能范围,快速找到满足特定条件的最优解。与标准二分查找(用于在有序数组中查找元素)不同,二分答案应用于更广泛的优化场景,其中我们猜测一个候选答案,检查其可行性,然后调整搜索范围。下面我将逐步解释其原理、使用场景,并提供一个代码示例,帮助您全面理解。 #### 1. 二分答案算法原理 二分答案的核心思想是将一个优化问题转化为对答案二分搜索。假设我们有一个函数 \( f(x) \) 表示某种指标(如成本、时间或资源),目标是找到最小或最大的 \( x \) 值,使得 \( f(x) \) 满足给定的约束条件(例如,\( f(x) \geq k \) 或 \( f(x) \leq k \))。算法步骤如下: - **初始化搜索范围**:确定答案的可能范围,通常设置左边界 \( L \)(最小值)和右边界 \( R \)(最大值)。例如,在资源分配问题中,\( L \) 和 \( R \) 可能表示资源的最小和最大可能值。 - **迭代搜索**:通过二分法不断缩小范围: 1. 计算中点值:\( \text{mid} = \left\lfloor \frac{L + R}{2} \right\rfloor \)。 2. **检查可行性**:使用一个辅助函数 \( \text{check}(\text{mid}) \) 判断 \( \text{mid} \) 是否满足问题约束(例如,是否可行)。如果可行,说明答案可能更小(或更大,取决于问题),调整边界;否则,向反方向调整。 3. **更新边界**: - 如果目标是**最小化** \( x \)(例如,找最小满足条件的值),且 \( \text{check}(\text{mid}) \) 为真,则设置 \( R = \text{mid} \)(因为答案在左半部分);否则,设置 \( L = \text{mid} + 1 \)。 - 如果目标是**最大化** \( x \)(例如,找最大满足条件的值),且 \( \text{check}(\text{mid}) \) 为真,则设置 \( L = \text{mid} \);否则,设置 \( R = \text{mid} - 1 \)。 - **终止条件**:当 \( L < R \) 时继续迭代;循环结束时,\( L \)(或 \( R \))即为最优解。 数学上,这个过程可以表示为对单调函数的二分搜索。例如,对于最小化问题,如果 \( f(x) \) 是单调非减函数,则算法保证在 \( O(\log(\text{range})) \) 时间内找到解,其中 \( \text{range} = R - L \) 是初始范围。二分答案的高效性源于其对数级时间复杂度,远优于暴力搜索的线性时间[^2]。 #### 2. 使用场景 二分答案算法广泛应用于需要优化“阈值”的问题中,特别是在约束条件下寻找最优解。常见场景包括: - **资源分配问题**:例如,在任务调度中,最小化最大完成时间(即“最小化最大值”)。假设有 \( n \) 个任务和 \( m \) 台机器,目标是分配任务使最长机器的运行时间最小。二分答案用于猜测最大时间 \( T \),并检查是否能在 \( T \) 时间内完成所有任务。 - **网络与图论**:在图中寻找最短路径的最大边权(例如,“最小化最大边权”问题),或判断是否存在路径满足最大延迟不超过某个值。二分答案通过猜测边权阈值,并检查图的连通性来实现。 - **数值优化**:如寻找满足条件的最小整数解,例如在数组中找第一个缺失的正整数,或求解方程 \( x^2 \geq k \) 的最小整数 \( x \)。 - **实际应用**:在算法竞赛和工程中,如CSP-J(信息学竞赛)中的问题,二分答案常用于处理数据范围大的优化任务,因为它能将时间复杂度从 \( O(n^2) \) 降低到 \( O(n \log n) \) 或更好[^2]。 这些场景的共同点是问题具有单调性:即如果某个值 \( x \) 可行,则所有更大(或更小)的值也可能可行,这使二分搜索有效。 #### 3. 代码示例 以下是一个简单的Python示例,演示二分答案算法求解“最小化最大值”问题:给定一个数组和一个整数 \( k \),将数组分成 \( k \) 个连续子数组,使得最大子数组和最小化。 ```python def check(mid, nums, k): """检查是否能在最大子数组和不超过mid的情况下,将数组分成k个子数组""" count = 1 # 当前子数组数量 current_sum = 0 for num in nums: if current_sum + num <= mid: current_sum += num else: count += 1 current_sum = num if count > k: # 如果子数组数量超过k,则不可行 return False return True def binary_search_answer(nums, k): """使用二分答案找到最小化最大子数组和""" L = max(nums) # 左边界:单个元素的最大值 R = sum(nums) # 右边界:整个数组的和 while L < R: mid = (L + R) // 2 if check(mid, nums, k): R = mid # 可行,尝试更小的值 else: L = mid + 1 # 不可行,增大值 return L # 最优解 # 示例使用 nums = [7, 2, 5, 10, 8] k = 2 result = binary_search_answer(nums, k) print(f"最小化最大子数组和为: {result}") # 输出: 18 ``` **代码说明**: - `check` 函数验证候选值 `mid` 是否可行(即能否将数组分成 `k` 个子数组,每个和不超过 `mid`)。 - `binary_search_answer` 函数实现二分搜索:初始化边界后,迭代调整直到找到最小可行的 `mid`。 - 时间复杂度为 \( O(n \log(\text{sum(nums)})) \),高效处理大数组。 #### 4. 注意事项 - **单调性要求**:二分答案要求可行性函数具有单调性(例如,如果 \( x \) 可行,则所有 \( y > x \) 也可行)。如果问题不满足单调性,算法可能失败。 - **边界处理**:初始边界 \( L \) 和 \( R \) 的选择需覆盖所有可能解,避免遗漏。 - **性能优势**:在数据规模大时(如 \( n \geq 10^5 \)),二分答案通常比动态规划或贪心算法更高效[^3]。 通过以上解释,您应该对二分答案算法有了清晰理解。如果您有具体问题或更多细节需求,请随时补充!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值