P3853 [TJOI2007]路标设置 (二分答案)
什 么 最 大 值 最 小 , 最 小 值 最 大 — — 二 分 就 完 了 ! 什么最大值最小,最小值最大——二分就完了! 什么最大值最小,最小值最大——二分就完了!
题目描述:
解题思路:
一般遇见对答案有最优要求,且有各种奇奇怪怪(一看就想让人去模拟)的条件,都可以用复杂度为O(log n)的二分答案来高效查找答案。
分析:
看到这种题目,我们可以直接暴力枚举加剪枝枚举最优答案,然后愉快超时……
我们为什么对于这种题目不暴力呢?因为怕超时?错!是出于程序猿的尊严,暴力太没逼格了!
对于粗暴的枚举,我们更愿意接受优雅的二分答案
相信各位对于什么二分思路啊,二分查找什么的都不陌生,这里就不过多阐述了,直接上模板!
- 如果真的想零基础学习二分,那么戳他!
二分模板
void search(int left,int right)
{
int mid,ans;
while(left<=right) //边界条件很重要,主要看题目要求,我喜欢这么写
{
mid=left+(right-left)/2; //二分查找技巧,防止数据溢出
//check函数根据题目来写内容,基本上就是模拟题目的某个步骤的实现过程,最后判断是否可行
if(check(mid))
{
ans=mid; //考虑当前数为答案
left=mid+1; //基础二分改变左右边界
}
else right=mid-1;
}
}
模板在手,二分我有( AC瞬间 )
没错,只要有模板,二分查找本身是人都会。但是,往往二分题目的难点并不仅仅在于边界条件的设定,而更在于二分算法中那个传说中的 check 函数!!
二分中的check ,顾名思义,就是通过各种玄学算法(搜索、模拟、贪心、DP等)来判断这个当前要试的mid是否合法,如果合法则根据这个题目的条件改变二分的左右界,最后找到最优的答案。
因此,check函数函数的重要性以及编写难度也就一目了然了。
那,到底难在什么地方呢?
要准确的明白此时此刻,你在二分查找的量到底是什么,只有明白了二分两边的单调性才能知道你编写的check函数到底要用什么算法,要做什么。
解题思路(这才是正解!!!):
就拿上面的例题来说吧,题目要求我们要让路标之间的空旷指数最小,也就是说让相邻距离最大的两个路标之间的距离尽量小。
这时候,我们要二分枚举的量是什么呢?是当前可能的空旷指数啊!我们通过二分找出一个mid,把他设为当前可能的空旷指数,然后用check函数判断这个指数是否合法(就是说当前空旷指数是否可行),如果合法,那么就说明当前的mid 可能是本题的最优解,把他记下。然后将右边界向左移动,继续二分。
为什么合法就要像左移动右边界呢?因为如果合法的话,根据二分的单调两段性,我们可以知道在当前 mid 右边的所有数,哪怕合法,也只会是比当前 mid 要大的数。而根据题目我们要求的是最小的 mid ,所以答案当然不可能存在于 当前 mid 的右边),如果不合法则反之。
那么难点又来了,这个判断当前mid是否合法的check函数到底怎么写呢?其实这里可以用到一个二分答案check 函数中的一个通用算法——模拟大法!我们可以尝试从第一个路标往后,在每两个路标之间判断,如果这两个路标之间的距离大于了当前尝试的 mid ,那么我们就在这里塞路标,尽可能的让这里的距离小于 mid。如果最后插入的总路标数仍然小于规定的路标数,那么表示当前空旷指数是可行的。否则表示当前空旷指数太小了,不可行。
check函数 CODE
bool check(int x) //X表示当前要尝试的空旷指数
{
int k,pos; //POS表示当前路标的位置,K表示还能设定的路标数目
k=K;
pos=a[0]; //当前路标设为起点
for(int i=1;i<=N;)
{
if(a[i]-pos<=x) //如果当前路标与下一个路标之间的距离小于X,说明可行
{
pos=a[i]; //改变当前路标
i++;
continue;
}
pos=pos+x; //如果当前路标与下一个路标的距离的空旷指数大于X,那么说明要设定一个路标在这里
k--; // 由于要新设定一个路标,所以路标数-1
if(k<0) return false; //如果可以新设的路标数为0了,那么直接退出,表示当前空旷指数不可行
}
return true; //如果一路顺风,那么可行
}
最后为水题小伙们附上CODE
#include <bits/stdc++.h>
using namespace std;
int L,N,K,a[100010],ans;
bool check(int x)
{
int k,pos;
k=K;
pos=a[0];
for(int i=1;i<=N;)
{
if(a[i]-pos<=x)
{
pos=a[i];
i++;
continue;
}
pos=pos+x;
k--;
if(k<0) return false;
}
return true;
}
int search(int l,int r)
{
int mid;
while(l<=r)
{
mid=l+(r-l)/2;
if(check(mid))
{
ans=mid;
r=mid-1;
}
else l=mid+1;
}
return ans;
}
int main()
{
scanf("%d%d%d",&L,&N,&K);
a[0]=0;
a[N+1]=L;
for(int i=1;i<=N;i++)
scanf("%d",&a[i]);
search(1,L);
printf("%d",ans);
return 0;
}
总结:
所以说,二分答案的题目,困难的从来不是上下边界的设定,而是那个迷之 check 函数的编写。
由此可见,一个好的二分模板和好的check 思路,能让你诞生 AC瞬间
对于这题,洛谷上还有一个双倍经验,快去水!!
如果你喜欢我的内容,那么也请支持一下他吧
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!