目录
2022/3/23更新,每一长段冗长的代码下面的那短短的二分函数,真的不用考虑太多边界啥的,死循环啥的。枉我以前看到二分就吓得躲开。浪费了多少时间呀!方法比努力重要!!
二分题目及其总结.
二分题目及其总结.
mp.lower_bound(value);
对数组进行二分查找,返回下标
lower_bound(a,a+n,value)-a;
1、贷款原值为n,每个月实际要还m,k个月还完,利率为x
可以得到等式关系pow(1.0/(1.0+x),k)==1-(n/m)*x)
bool find(double x){
return (pow(1.0/(1.0+x),k)>=1-(n/m)*x);//如果大于 向左查找,反之向右查找
}
2、之前找两个序列中重叠的值老是要循环遍历,想先排序增加效率,还要求你按在其中一个原序列中的顺序输出
这下好了,其中一个保持原序列,另一个先排序再搜索(归并搜索,二分都建立在排序的基础山),通过map数组记录该有序序列中出现的元素,按照第一个原序序列,分别判断是否在mp数组中出现过。
mp[a[i]]!=0
mp.find(a[i])!=string::npos说明找到了
mp.find(a[i])没找到返回-1(非零的),不能通过if(mp.find(a[i]))判断
string:npos是个特殊值,说明查找没有匹配
03:矩形分割
03:矩形分割
理解题目还挺重要的 ,不然一直在那儿白费脑力
描述
平面上有一个大矩形,其左下角坐标(0,0),右上角坐标(R,R)。大矩形内部包含一些小矩形,小矩形都平行于坐标轴且互不重叠。所有矩形的顶点都是整点。要求画一根平行于y轴的直线x=k(k是整数) ,使得这些小矩形落在直线左边的面积必须大于等于落在右边的面积,且两边面积之差最小。并且,要使得大矩形在直线左边的的面积尽可能大。注意:若直线穿过一个小矩形,将会把它切成两个部分,分属左右两侧。
一开始看题,既要左右面积差值最小又要大矩形左侧面积最大,像极了数学里高深的优化问题,难不成还得来个多元线性回归?!
不重叠,说明 要求大矩形左侧面积尽可能大,只是建立在 已经找到小矩形们左右面积差最小的位置 这一条件之上,如果找到的位置x可以向右移动并且不改变左右侧小巨星们的面积(怎么才能实现这一条件呢?自然是这个位置位于两个小矩形之间的留白处,那么就将x进一步向右调整到下一个小矩形的左侧,跨过整个留白区, 或者是跨国最后一个小矩形与大矩形右边界的留白区),这种操作可以视作两种情况,也可合二为一
2023更新:左侧面积大于等于右侧面积(条件1)且面积差尽可能地小(条件2)==找到最小的满足(条件1)的分界点
优先考虑以上两个条件,在以上两个指标都不变的基础上,要使得左侧面积尽可能大,自然就是找到以上分界点后,向右挪过空白区
while(areaD(x)==areaD(x+1)&&x<a){
x++;
}//x可以取到大矩形右边界a
cout<<x;
#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
ll a;//大正方形右上角坐标
ll n;//n个小矩形
//int lx,ly,w,h;//每个小矩形左上角坐标和宽、高
// int a[MAX][4];
struct matrix{
ll l,u,w,h;//左上宽高
}m[10001];
ll Size=0;//总面积
ll areaD(int x){
ll leftS=0;
for(int i=0;i<n;i++){
if(m[i].l<x){
if(m[i].l+m[i].w<=x)leftS+=m[i].w*m[i].h;
else leftS+=(x-m[i].l)*m[i].h;
}
}
ll differ=leftS-(Size-leftS);
return differ;
}
ll search(int left,int right){
ll mid;
while(left<=right){
mid=left+(right-left)/2;
if(areaD(mid)>0)right=mid-1;
else if(areaD(mid)<0)left=mid+1;
else return mid;
}
return left;
}
int main(int argc, char** argv) {
cin>>a>>n;
for(int i=0;i<n;i++){
cin>>m[i].l>>m[i].u>>m[i].w>>m[i].h;
Size+=m[i].w*m[i].h;
}
ll x=search(0,a);
while(areaD(x)==areaD(x+1)&&x<a){
x++;
}//x可以取到大矩形右边界a
cout<<x;
return 0;
}
ll search(int left,int right){
ll mid;
while(left<right){
mid=left+(right-left)/2;
// if(areaD(mid)>0)right=mid-1;
// else if(areaD(mid)<0)left=mid+1;
// else return mid;
if(areaD(mid)>=0)right=mid;
else left=mid+1;
}
return left;
}
总而言之:
1、能找到面积相等的位置,就继续探寻是否能跨过一个留白区,找到满足“左侧面积 = =右侧面积”的最大整数值
2、对于右侧面积为0的:最终值 = 大矩形的边界 r
3、若不属于以上两种情况,就通过while(left<=right)这种二分寻找跳出循环后的left,就是使左右侧面积最接近的位置
还有一个迷惑问题,search函数中将else if改成if,结果惊人1000???
接下来关注到二分查找的细节
二分查找的截止判断条件
-
while(left<=right)……
-
while(left<right)……
-
while(left<right-1)……
找到了mid倒无甚区别,,如果没找到还需要依赖left与right的final值来确定与所求值最接近的数据的一个范围
NO.3:
跳出循环后,left == right-1,left与right紧紧相邻
需要二分枚举的答案是整数,所以可以用这个方式结束,
像这题,还需要分别判断left 和 right哪个更能使左右小矩形们面积之差靠近
并且在循环体内部
if (sum>0) r=mid;
else if (sum<=0) l=mid;
else if (sum==0) {printf(“%d”,mid);return 0;}
左右边界值的修改是这样进行的
NO.1
跳出循环后
在循环内部这样修改边界值
while(left<=right){
mid=left+(right-left)/2;
if(areaD(mid)>0)right=mid-1;
else if(areaD(mid)<0)left=mid+1;
else return mid;
}
return left;NO.2
while(left<right)……
在循环内部这样修改边界值
if(leftS<=rightS) //为了尽量让k靠右,需要用小于等于
{
l=mid;
}
if(leftS>rightS) //为了尽量减少面积差
{
r=mid;
}在循环体内部,可以进行最后一次二分进行判断
if(r-l==1) //经过二分后的最后判断NO.1、2都默认跳出循环之后/最后一次循环中,left是最接近寻找的值
如果mid可能包括要找的值和其他,那么修改边界值(目的是继续寻找边界,边界就要取上一次的mid
while(l<r){/*lower_pound()二分求下界*/
mid=l+(r-l)/2;
if(2*sa[mid]>=sa[R])r=mid;//必须如此,不可r=mid-1;亲测错误
else l=mid+1;
}
return l;
}
NO.1、2都默认跳出循环之后/最后一次循环中,left是最接近寻找的值
也就是要寻找的值可能会出现在边界值里
如果将NO.1的mid+/-1改成mid。将NO.2的mid+1改成mid,都会超时
好的,乱了
关于修改边值条件,到底是mid+/-1 还是 mid, 要回到二分法最初的思想,就是在target出现的更小的范围内继续二分,也就是说,接下来划定的范围无论如何一定要包括target 但是已经排除的范围不能再加到下一次二分的范围。
这题target是areaD(mid)==0,或者是areaD(mid)>0的范围里取一个 最接近mid的
一、
每次二分都是奔着mid=0去的,因此mid不满足条件,就不用再加入下次二分的范围内
最后一次二分,left==right=mid,mid还不满足条件,那必定是mid-1小于0,mid+1大于0,根据题意我们选的是后者
这种二分方式,循环结束后left是比所求得target稍大得那个
二、
,mid小于等于0包含了mid==0,mid也许就是最后的target,故需要加入下次二分的范围内。 每次二分其实是为了找到mid>0得最小mid去的,因此mid>0符合所求,故加入下一次二分范围。
不同于NO3的是跳出循环后,mygod
三、
每次二分其实是为了找到mid>0得最小mid去的,因此mid>0符合所求,故加入下一次二分范围,mid小于等于0包含了mid==0,mid也许就是最后的target,故需要加入下次二分的范围内
跳出循环后,left<right并相邻,并不知道谁更靠近target所以要判断比较
05:派
05:派
#include <bits/stdc++.h>
using namespace std;
//#define pi 3.14
const double pi=acos(-1.0);
//π值用cmath头文件中的acos(-1.0)也可以求出来。
#define MAX 10001
double r[MAX];
double s[MAX];
int n,f;
int enough(double x){//假设每个人分半径x
int cnt=0;
double si;
for(int i=0;i<n;i++){
cnt+=floor(s[i]/x);
}
return cnt-(f+1);//enough(x)<0代表每人x不够分
}
double search(double left,double right){
double ans;//用于记录满足里面最大的
while(right-left>1e-5){//每个人分到的最大派的半径范围里找enough且最大的那个
double mid=left+(right-left)/2 ;
/* if(enough(mid)<0)right=mid-1;
// else if(enough(mid)>=0)left=mid;这是满足的,要在里面找最大的 ,
//可不可以像划分举行那样先找到刚好enough的再向右移动呢,不好!这样移动不如继续二分
//失去了二分本来的节约时间的目的
else if(enough(mid)>0)left=mid+1;
else return mid;//找到刚好够分的 */
if(enough(mid)>=0){
ans=mid;//不断右分过程中找最大的
left=mid;
}
else right=mid-0.00001; //不能是mid减1噢,这可是浮点数
}
// return left; //这里则总能找到mid,因为是double,不用思索跳出循环后left咋样
if(enough(left)>=0) ans=left;
return ans;
}
int main(int argc, char** argv) {
cin>>n>>f;
//int m=f+1;//n块派分给m个人,每人分一整个派/一个派的一块,每人大小相等
// int maxS=0;
for(int i=0;i<n;i++){
cin>>r[i];
s[i]=pi*r[i]*r[i];
// if(s[i]>maxS)maxS=s[i];
}
sort(s,s+n);
cout<<fixed<<setprecision(3)<<search(0,s[n-1]);
return 0;
}
double search(double left,double right){
while(right-left>1e-5){
double mid=left+(right-left)/2 ;
if(enough(mid)>=0){
left=mid;
}
else right=mid; //不能是mid减1噢,这可是浮点数
}
return left;
}
//一、pi的值acos 二、double类型的比较 要用1e-1 三、每个人可以分一整个派多加
//别的派吗,二分的最大值是最小的那块派面积才对呀,好吧,经过提交还是最大的那块,
//不理解 二分要求的是面积,那就用面积二分吧,本来就是再0.0000001那么小的间距二分,
//根本承受不起半径计算面积再考虑精度带来的误差
06:月度开销
#include <bits/stdc++.h>
using namespace std;
#define MAX 100010 //眼神不好就直接赋值题干吧,runtime error
int a[MAX];
int n,m;//代表总天数和“月”数
int adequate(int x){//x是待试的最小的fajio月度开销
// int month=0;
int month=1;
// //啊啊啊啊啊,第一个月压根没考虑进去呀 分好了第一个月后month还为0
int temp=0;//累加每个月的开销
for(int i=0;i<n;i++){
if(temp+a[i]<=x){//1、等于也是可以算在这个月的呀
temp+=a[i];
}
else {
temp=a[i];//把第i天的开销算在下一个月
month++;
}
}
if(month>m)return 0;//说明定下的月度开销x
else return 1;
//2、month大了说明暂定的月度开支的偏小了,不行
记住满足条件是 这些月份都能包下,甚至更少的月
}
int search(int left,int right){
int ans=0;
while(left<=right){
int mid=left+(right-left)/2;
if(adequate(mid)){
// printf("niah2\n");
right=mid-1;//因为mid可能是符合条件的值
// 但没关系,ans已经记录了mid,如果接下来没找到,那就是ans了
ans=mid;//在符合条件的范围里头找最小的
}
else {
left=mid+1; //上述分析mid娶不到
}
}
return ans;
}
int main(int argc, char** argv) {
cin>>n>>m;
int maxx=0;
int sum=0;
for(int i=0;i<n;i++){
cin>>a[i];
if(a[i]>maxx)maxx=a[i];
sum+=a[i];
}
cout<<search(maxx,sum)<<endl;
return 0;
}
int search(int left,int right){
while(left<right){
int mid=left+(right-left)/2;
if(adequate(mid)){
right=mid;
}
else left=mid+1;
}
return left;
}
些许离谱,离谱到有些难以置信
- 对于month的初始化的值是0还是1,思维定势地初始化为0,而实际上其实就对第一个月分配天数。
- 对于题目给出的数据范围,少看了一个0导致出现runtime error,眼神不好,请求手手直接赋值题干吧
关于在oj上出现Runtime error 的情况
这个挺好,虽然只是字面上提示了我,与我犯的错误本质不同,但学到了:在函数里使用了太大的数组,函数里的数组作为局部变量存放在栈区,也有可能会Runtime error,解决方法是在全局区开这个数组 - 关于二分的条件函数adequate,其实返回的true和false对应的情况是谁并不重要,本就是用来判断下一次二分的范围的。但为了代码的含义考虑,最好true还是代表满足二分所需的条件。
10:河中跳房子
#include <bits/stdc++.h>
using namespace std;
#define MAX 50010
typedef long long ll;
ll d,n,m;
ll a[MAX];//每个岩石与起点的距离
bool adequate(int x){//一次条约过程中的 最短跳跃距离
int cnt=0;//移除几块石头
int now=0;
for(int i=1;i<=n+1;i++){
if(a[i]-a[now]>=x){//够得着最短条约距离继续往后跳,
// 够不着最小条约距离就移除石头之后就能够着了
now=i;
cnt++;
}
}
// if(cnt>n-m+1) //太短了,要往长处加 ,剩下的石头太多了
if(cnt<=n-m)return false;
else return true;
}
int search(int left,int right){
int ans=0;
while(left<=right){
int mid=left+(right-left)/2;
if(adequate(mid)){
left=mid+1;
ans=mid;
}
else{
right=mid-1;
}
}
return ans;
}
int main(int argc, char** argv) {
cin>>d>>n>>m;
a[0]=0;
for(int i=1;i<=n;i++){
cin>>a[i];
}
a[n+1]=d;
cout<<search(1,d);
return 0;
}
思考方式,进入
int k=n-m+1;//移除m个岩石后起点与岸之间还剩 k段距离 ,求这k段距离里
求最大月度开销的最小值,月的划分方式很多种,每种方式得到一系列 阅读开销,求这一系列阅读开销里最大的值最小的那种方式
这里求最长可能的最短跳跃距离,划分距离的方式很多种(已知划分为几段)
每种方式得到一系列条约距离,其中总存在一个最小的距离,求哪种划分方式使这个最小距离最大
2456:Aggressive cows
这题算 最短距离的最大值和河中跳房子是简直一模一样了,其中,不管要求最短距离的最大值,还是求最大阅读开销的最小值,在adequate函数中,传进去的参数就要肯定得当作最短距离或者是最大月度开销,假设这个成立,再根据这个条件去划范围,找另一个最短距离或者是最大月度开销。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define MAX 100010
int n,m;
ll a[MAX];
bool adequate(int x){//此时我应该肯定!x就是最短距离!再做事
int cnt=1;//牛的数量 第一个马厩肯定放一个
int interval=0;
for(int i=1;i<n;i++){
interval+=a[i]-a[i-1];
if(interval>=x){
cnt++;//牛放在i这个马厩
interval=0;//放好了一头牛,接下来的距离又变成0
}
if(cnt==m)return true;
}
// if(cnt<m)return false;//放不下n头牛,最短距离太大了
// else return true;
// if(cnt==m)return true;
return false;
}
int search(int left,int right){
int ans=0;
while(left<=right){
int mid=left+(right-left)/2;
if(adequate(mid)){
ans=mid;
left=mid+1;
}
else{
right=mid-1;
}
}
return ans;
/**********************************
**或者不用ans,最后返回left-1,跳出循环后的left比最后得到的mid大1码!!!**
***********************************/
}
int main(int argc, char** argv) {
cin>>n>>m; //求最大的最小距离
memset(a,0,sizeof(a));
for(int i=0;i<n;i++){
// cin>>a[i];Huge input data,scanf is recommended.
scanf("%d",&a[i]);
}
sort(a,a+n);
int r=a[n-1]-a[0];
cout<<search(0,r);//两头牛之前距离的最大范围
return 0;
}
由于求的是最短距离的最大值,因此
07:和为给定数
十分和八分,自行体会,不理解 英语作业没写啊啊啊啊啊啊啊
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define MAX 100010
ll n,m;
ll a[MAX];
ll x;
bool search(int left,int right){
while(left<=right){
ll mid=left+(right-left)/2;
if(a[mid]<x){
left=mid+1;
}
else if(a[mid]>x){
right=mid-1;
}
else{
return true;
}
}
return false;
}
int main(int argc, char** argv) {
// scanf("%d",&n);
cin>>n;
for(int i=0;i<n;i++){
// scanf("%d",&a[i]);
cin>>a[i];
}
cin>>m;
sort(a,a+n);
// ll ri=a[n-1];
for(int i=0;i<n-1;i++){
x=m-a[i];
if(search(i+1,n-1)){//寻找m-a[i],从a[i+1]开始找
cout<<a[i]<<" "<<x;
return 0;
}
}
printf("No");
return 0;
}
八分,十分运行耗得内存小
一个传了数组下标进去二分,一个传了数组的值,前者十分儿,Why?
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define MAX 100010
ll n,m;
ll a[MAX];
ll x;
bool search(int left,int right){
while(left<=right){
ll mid=left+(right-left)/2;
if(mid<x){
left=mid+1;
}
else if(mid>x){
right=mid-1;
}
else{
return true;
}
}
return false;
}
int main(int argc, char** argv) {
// scanf("%d",&n);
cin>>n;
for(int i=0;i<n;i++){
// scanf("%d",&a[i]);
cin>>a[i];
}
cin>>m;
sort(a,a+n);
ll ri=a[n-1];
for(int i=0;i<n-1;i++){
x=m-a[i];
if(search(a[i+1],ri)){//寻找m-a[i],从a[i+1]开始找
cout<<a[i]<<" "<<x;
return 0;
}
}
printf("No");
return 0;
}
不行了,再来一波!!!
Cable master
浮点型二分法转化为整数进行:新技巧:以后对于这类的浮点数的二分时,将其转化成整型进行二分;(如果扩大倍数的,之后别忘再缩回来))
大致意思是:有n根电缆,需要裁成k根长度相同的电缆,问这个长度最大取多少 ?
困惑之处: 为什么search函数返回l和mid都wa, 跳出循环时r就是无限接近于l呀???
学习笔记:
【1】、直接保留两种小数的做法会四舍五入 ,
解决:先化作整数用floor取整,再恢复小数点位置。
【2】、
eps不能设置的太小,不然如果小于double的精度的话会导致死循环问题。一般eps设为1e-7即可。
【3】、
这题用cin输入会 tle,小老师说朝时就换成scanf,挺管用,有时间还是得好好看看 cin.tie(0)怎么用
易错示警:
浮点型二分法转化为整数进行,扩大倍数之后要缩回来,这就涉及到整数转化成浮点型,double(search(1,maxx)/100.0)
其中这个100.0 是非常必要的,由于要保留两位小数所以一开始将浮点数扩大成了100倍的整数,因此整数的每一位都是有效数字。如果直接两个整型数据相除,轻易就会失去有效数据,这时就需要让这种运算的精度提升到浮点型的运算精度,不会失去有效数据。**
printf("%.2f",floor(search(0,maxx)*100)/100);
// cout<<search(0,maxx);//2.005
// printf("%.2f",search(0,maxx));这种直接保留两种小数的做法会四舍五入
// cout<<fixed<<setprecision(2)<<search(0,maxx);
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <math.h>
#include <iomanip>
using namespace std;
double a[10005];
int n,k;//有n根电缆,需要裁成k根长度相同的电缆,问这个长度最大取多少
bool adequate(double x){
int cnt=0;
for(int i=0;i<n;i++){
cnt+=int(a[i]/x);
}
if(cnt>=k)return true;
else return false;
}
double search(double l,double r){
double mid;
while(r-l>1e-10){// while(fabs(r-l)>1e-7){
mid=(l+r)/2;
if(adequate(mid)){
l=mid;
}
else r=mid;
}
return r;//不明白,为什么返回l和mid都wa,跳出循环时r就是无限接近于l呀???
}
int main(){
cin>>n>>k;
for(int i=0;i<n;i++){
// cin>>a[i];
scanf("%lf",&a[i]);
}
sort(a,a+n);
double maxx=a[n-1];
printf("%.2f",floor(search(0,maxx)*100)/100);
// cout<<search(0,maxx);//2.005
// printf("%.2f",search(0,maxx));这种直接保留两种小数的做法会四舍五入
// cout<<fixed<<setprecision(2)<<search(0,maxx);
return 0;
}
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <math.h>
#include <iomanip>
using namespace std;
int a[10005];
int n,k;//有n根电缆,需要裁成k根长度相同的电缆,问这个长度最大取多少
bool adequate(int x){
int cnt=0;
for(int i=0;i<n;i++){
cnt+=int(a[i]/x);
}
if(cnt>=k)return true;
else return false;
}
int search(int l,int r){
int ans=0;
while(r>=l){
int mid=(l+r)/2;
if(adequate(mid)){
l=mid+1;
ans=mid;
}
else r=mid-1;
}
return ans;
}
int main(){
cin>>n>>k;
double x;
for(int i=0;i<n;i++){
scanf("%lf",&x);
a[i]=int(x*100);
}
sort(a,a+n);
int maxx=a[n-1];
cout<<fixed<<setprecision(2)<<double(search(1,maxx)/100.0);
return 0;
}
也不用强求啦,浮点型的二分也不过而已(主要是懒得想转化为整数型是否更简便了
Strange fuction
#include <bits/stdc++.h>
using namespace std;
double fun(double x,double y){
return 6*pow(x,7)+8*pow(x,6)+7*pow(x,3)+5*pow(x,2)-x*y;
}
double f(double x,double y){
return 42*pow(x,6)+48*pow(x,5)+21*pow(x,2)+10*pow(x,1)-y;
}
double y;
double search(double l,double r){
double mid=0;
while(r-l>1e-6){
mid=(l+r)/2;
double temp=f(mid,y);
// if(temp<=1e-6){
// ans=mid;
// return ans;
// }
// else
if(temp>0){
r=mid;
}
else if(temp<0){
l=mid;
}
}
return mid;
}
int main(){
int t;
cin>>t;
while(t--){
cin>>y;
double l=0;
double r=100.0;
cout<<fixed<<setprecision(4)<<fun(search(l,r),y)<<endl;
}
return 0;
}
The Frog’s Games
#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
ll a[500005];//已知每块石头距离岸边的距离,求每段距离存放
ll d[50005];
ll L,n,m;//河宽L,n快石头,最多跳m次
bool adequate(int x){//假设 最大条约举例 为x,看看m次能否跳过河水
if(m*x<L)return false;
int cnt=0;//跳多少次
ll s=0;
for(int i=1;i<=n+1;i++){
if(d[i]>x)return false;
s+=d[i];
if(s>x){
i--;
cnt++;
s=0;
if(cnt>=m)return false;
}
}
return true;
}
ll search(int l,int r){
ll ans=0;
while(l<=r){
int mid=l+(r-l)/2;
if(adequate(mid)){//如果neng过河,最大条约距离还可以缩小
ans=mid;
r=mid-1;
}
else l=mid+1;
}
return ans;
}
int main(){//求最小的最大条约距离
while(cin>>L>>n>>m){
memset(a,0,sizeof(a));
memset(d,0,sizeof(d));
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
sort(a+1,a+n+1);//n块石头从进到远排好序
for(int i=1;i<=n;i++){
d[i]=a[i]-a[i-1];
}
d[n+1]=L-a[n];//n快石头,n+1段距离
cout<<search(1,L)<<endl;
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
ll a[500005];//已知每块石头距离岸边的距离,求每段距离存放
ll L,n,m;//河宽L,n快石头,最多跳m次
bool adequate(int x){//假设 最大条约举例 为x,看看m次能否跳过河水
if(x*m<L)return false;
int cnt=0;//跳多少次
int pos=0;
for(int i=1;i<=n+1;i++){
//if(a[i]-a[i-1]>x)return false;//一段距离就已经大于最大条约距离了,肯定不行
if(a[i]-a[pos]>x){
if(i-pos==1)return false;
cnt++;
i--;
pos=i;
if(cnt>=m)return false;
}
}
return true;
}
int search(int l,int r){
int ans=0;
while(l<=r){
int mid=l+(r-l)/2;
if(adequate(mid)){//如果neng过河,最大条约距离还可以缩小
ans=mid;
r=mid-1;
}
else l=mid+1;
}
return ans;
}
int main(){//求最小的最大条约距离
while(cin>>L>>n>>m){
memset(a,0,sizeof(a));
for(int i=1;i<=n;i++){
//cin>>a[i];
scanf("%d",&a[i]);
}
sort(a+1,a+n+1);//n块石头从进到远排好序
a[n+1]=L;
cout<<search(1,L)<<endl;
}
return 0;
}
真的是,以为这里错了那里错了,试错到最后,发现根本不管他们的事情。
sort函数为数组元素排序, sort(a+1,a+n+1);与 sort(a,a+n);
仅此而已。。。卒
小总结们
目的总是取到一个最合适的值。
首先,找到取值的范围,在该范围内进行二分。
判断取值是否满足题意条件
……
整数二分模板一共有两个,分别适用于不同情况。
算法思路:假设目标值在闭区间
[
l
,
r
]
[l, r]
[l,r]中, 每次将区间长度缩小一半,当
l
=
r
l = r
l=r时,我们就找到了目标值。
模板一:(再也不用为边界的处理而头痛了)
当我们将区间
[
l
,
r
]
[l, r]
[l,r]划分成
[
l
,
m
i
d
]
[l, mid]
[l,mid]和
[
m
i
d
+
1
,
r
]
[mid + 1, r]
[mid+1,r]时,其更新操作是
r
=
m
i
d
r = mid
r=mid或者
l
=
m
i
d
+
1
l = mid + 1
l=mid+1;,计算
m
i
d
mid
mid时不需要加1。
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
模板二:
当我们将区间
[
l
,
r
]
[l, r]
[l,r]划分成
[
l
,
m
i
d
−
1
]
[l, mid - 1]
[l,mid−1]和
[
m
i
d
,
r
]
[mid, r]
[mid,r]时,其更新操作是
r
=
m
i
d
−
1
r = mid - 1
r=mid−1或者
l
=
m
i
d
l = mid
l=mid;,此时为了防止死循环,计算
m
i
d
mid
mid时需要加1。
边界的处理:
死循环的解释是:例如二分区间缩小到 l=3,r=4 时,l=mid ,mid的算法一定是向上取整,否则一直取的是3
或者解释为,要保持一致的区间分割方式
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
这两种情况就是一个是从小到大找,一个是从大到小找。因为二分的前提是要有序(一般都是,搜索的范围中值是升序的,寻找满足条件的最小值,那么当adequate(mid)
,对应的就是 模板一 r=mid
,
相反,如果要找满足条件的最大值,那么当adequate(mid)
,对应的就是 模板二 l=mid
浮点数二分
bool check(double x) {/* ... */} // 检查x是否满足某种性质
double bsearch_3(double l, double r)
{
const double eps = 1e-6; // eps 表示精度,取决于题目对精度的要求
while (r - l > eps)
{
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;//不能是mid减1噢,这可是浮点数,1是精度的巨大倍
}
return l;
}