判断能否使用二分的条件
1、确定一个区间,使得目标值(答案)一定在区间里面
2、找一个性质满足(1)性质具有二段性 (2)答案是二段性的分界点
整数二分
第一类:ans是红色区间的右端点:将[L,R]分成[L,M-1],[M,R],如果M是红色的,说明ans仍然在[M,R]里面,否则说明ans在[L,M-1]里面
while(L<R)
{
M=(L+R+1)/2;//记得补上1,如果不补1,可以用两个数的情况来举反例
if M in 红色区间
L=M;
else
R=M-1;
}
第二类:ans是绿色区域的左端点:将[L,R]分成[L,M],[M+1,R],如果M是绿色的,说明ans在[L,M]之间的,否则说明ans在[M+1,R]里面。
while(L<R)
{
M=(L+R)/2;//记得补上1,如果不补1,可以用两个数的情况来举反例
if M in 绿色区间
R=M;
else
L=M+1;
}
总结:L=mid的时候需要加1,R=mid的时候不需要加1
例题1(很晓畅的整数二分,区间的处理)
数的范围
题目描述
给定一个按照升序排列的长度为n的整数数组,以及 q 个查询。
对于每个查询,返回一个元素k的起始位置和终止位置(位置从0开始计数)。
如果数组中不存在该元素,则返回“-1 -1”。
输入格式
第一行包含整数n和q,表示数组长度和询问个数。
第二行包含n个整数(均在1~10000范围内),表示完整数组。
接下来q行,每行包含一个整数k,表示一个询问元素。
输出格式
共q行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回“-1 -1”。
数据范围
1≤n≤100000 1≤q≤10000 1≤k≤10000
样例
输入样例:
6 3
1 2 2 3 3 4
3
4
5
输出样例:
3 4
5 5
-1 -1
下面贴个代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int q[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++)
{
cin>>q[i];
}
for(int i=0;i<m;i++)
{
int x;
cin>>x;
int l=0,r=n-1;
//首先找的是区间的左端点
while(l<r)
{
int mid=l+r>>1;//因为是r=mid所以不用加1
if(q[mid]>=x)r=mid;
else l=mid+1;
}
if(q[r]==x)//得判断退出循环后找没找到目标值,因为有可能给的目标值不在数组中
{
cout<<r<<" ";
r=n-1;//寻找区间的右端点,l其实可以不用处理,不过l=0也没什么影响
while(l<r)
{
int mid=(l+r+1)>>1;//因为是l=mid所以需要加1,有点像左加右不变
if(q[mid]<=x)l=mid;
else r=mid-1;
}
cout<<l<<endl;
}
else cout<<"-1"<<" "<<"-1"<<endl;
}
return 0;
}
在CSDN上面看到的一个不需要考虑mid+1、mid-1的二分查找模板
int L=-1,R=n;//这个边界不一样了,y总的是0和n-1
while(L+1!=R)//循环结束的条件也不一样了
{
int mid=L+R>>1;
if(check()) L=mid;
else R=mid;
//最后根据你所分左右两边区间的结果
//选取L或者R作为结果
}
为什么L的初始值为-1,R的初始值为N
首先,如果二分本来就没有结果
比如对于本文例题 1 2 2 3 3 4,,如果你要寻找第一个 >=5 的数,你会发现,整个过程都在执行L=mid,最后得到的结果中,R是等于下标6的,他明显这个时候是越界的,说明我们找不到要寻找的数字,而如果我们一开始将R赋值为n-1,也就是赋值为下标5的时候,他返回的R是5,是没有越界的,被我们当成了答案,但其实这时候我们的二分是没有答案的,就发生了错误;
其次,L最小值为-1,R最小值只能取到1,因为L+1!=R为循环结束条件,R最大值为N,同理则L的最大值为N-2,则(L+R)/2的取值范围是 [0,N)
mid的值始终位于0到N的左闭右开区间里面,不会发生越界的错误;为什么循环结束的条件是while(L+1!=R)?
之前学过二分的小伙伴可能会发现,之前学的二分,他循环结束的条件是while(L<R)
而这边给出的循环条件是while(L+1!=R) 其实,就是当L和R相邻的时候,循环就结束,而原本的while(L<R)
是当两区间重合以后,循环才结束,所以之前我们需要判断对mid进行加一或者减一的操作,而且因为区间重合的问题,最后返回的L、R还要再进行判断,而这边的这个二分,因为区间反回的是不重合的两区间,只有L=mid和R=mid这两种情况,最后根据需要返回L或者R;
木材加工
#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, k;
int a[N];
int ans;
bool check(int mid)
{
int num = 0;
for (int i = 0; i < n; i++)
{
num += int(a[i] / mid);
}
if (num >= k)return true;
else return false;
}
int main()
{
cin >> n >> k;
for (int i = 0; i < n; i++)cin >> a[i];
int l = 0, r = 100000001;
while (l < r)
{
int mid = (l + r+1) / 2;
if (check(mid))l=mid;
else r=mid-1;
}
cout <<l<< endl;
return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int n, k;
int a[N];
int ans;
//这个check我没想出来
bool check(int mid)
{
int sum=0,num=1;
for(int i=0;i<n;i++)
{
//由于分段的时候是连续的,所以可以直接这样分:满了就置零
if(sum+a[i]>mid)sum=0,num++;
//用很大或者很极端的情况来判断变量是否满足条件
sum+=a[i];
}
return num<=k;
}
int main()
{
cin>>n>>k;
int l=0,r=0;
for (int i = 0; i < n; i++)
{
cin >> a[i];
r+=a[i];//最大最大
l=max(l,a[i]);//至少至少
}
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid))r=mid;
else l=mid+1;
}
cout<<l<<endl;
return 0;
}
小数二分完全没有上述烦恼
给定一个浮点数 n,求它的三次方根。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
double l,r;
int main()
{
double n;
cin>>n;
l=-10000;
r=10000;
while(r-l>1e-8)
{
double mid=(l+r)/2;
if(mid*mid*mid>=n)r=mid;
else l=mid;
}
printf("%.6f",r);
return 0;
}
洛谷里面的一元三次方程的解
#define _CRT_SECURE_NO_WARNINGS 1
#include<bits/stdc++.h>
using namespace std;
double a, b, c, d;
double f(double x)
{
return a * x * x * x + b * x * x + c * x + d;
}
int main()
{
double l, r, mid, x1, x2;
cin >> a >> b >> c >> d;
int cnt = 0;
for (int i = -100; i < 100; i++)
{
l = i;
r = i + 1;
x1 = f(l);
x2 = f(r);
//左端点
if (x1 == 0) {
printf("%.2f ", l);
cnt++;//一个数量标记而已
}
if (x1 * x2 < 0)//必有解
{
while (r - l > 1e-4)
{
mid = (l + r) / 2;
if (f(mid) * f(r) <= 0)l = mid;
else r = mid;
}
printf("%.2f ", r);
cnt++;
}
if (cnt == 3)break;
}
return 0;
}
//做一下暴力的
//int main()
//{
// cin >> a >> b >> c >> d;
// for (double i = -100; i < 100; i += 0.001)
// {
// double j = i + 0.001;
// double y1 = a * i * i * i + b * i * i + c * i + d;
// double y2 = a * j* j * j + b * j * j + c * j + d;
// if (y1 >= 0 && y2 <= 0 || y1 <= 0 && y2 >= 0)
// {
// double x = (i + j) / 2;
// printf("%.2f ", x);
// }
// }
//
//
// return 0;
//}
…………………………………………
1.24续
今天做的这个题,一眼看上去好像不太像二分…(虽然题解说二分和递推都可以)
“遇到最值问题,思维模型:二分->dfs暴力->DP->贪心”,然后判断题目中潜在的二段性,这个730题的二段性实际上是能量超过一定值(设为E0),超过E0的初始能量值就一定满足,所以这个二分实际上就是找左端点。然后中规中矩写二分就行啦!
代码如下:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int l,r;
int h[N];
int n;
bool check(int e)
{
for(int i=1;i<=n;i++)
{
e=2*e-h[i];
if(e>=1e5)return true;
if(e<0)return false;
}
return true;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>h[i];
}
l=0,r=1e5;
while(l<r)
{
int mid=(l+r)/2;
if(check(mid))r=mid;
else l=mid+1;
}
printf("%d",r);
return 0;
}
分巧克力,这个题也就是相当于找右端点,然后难点?如果有的话,应该是巧克力分成的正方形的块数吧……
(h[i]/正方形边长)*(w[i]/正方形边长)就是块数
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int n,k;
int h[N];
int w[N];
bool check(int mid)
{
int sum=0;
for(int i=1;i<=n;i++)
{
int x=h[i]/mid;
int y=w[i]/mid;
sum+=x*y;
if(sum>=k)return true;
}
return false;
}
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>h[i]>>w[i];
}
int l=0,r=1e5;
while(l<r)
{
int mid=(l+r+1)/2;
if(check(mid))l=mid;
else r=mid-1;
}
printf("%d",r);
return 0;
}
https://www.acwing.com/problem/content/1223/四平方和https://www.acwing.com/problem/content/1223/
这个题难点其实是输出字典序!
bool operator< (const Sum& t)const
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=2500010;
struct Sum
{
int s,c,d;
bool operator< (const Sum& t)const
{
if(s!=t.s)return s<t.s;
if(c!=t.c)return c<t.c;
if(d!=t.d)return d<t.d;
}
}sum[N];
int n,m;
int main()
{
cin>>n;
for(int c=0;c*c<=n;c++)
for(int d=c;c*c+d*d<=n;d++)
sum[m++]={c*c+d*d,c,d};
sort(sum,sum+m);
for(int a=0;a*a<=n;a++)
{
for(int b=a;b*b+a*a<=n;b++)
{
int t=n-a*a-b*b;
int l=0,r=m-1;
while(l<r)
{
int mid=(l+r)/2;
if(sum[mid].s>=t)r=mid;
else l=mid+1;
}
if(sum[r].s==t)
{
printf("%d %d %d %d",a,b,sum[r].c,sum[r].d);
return 0;
}
}
}
return 0;
}
STL里面的unordered_map的使用,更加简单
#include <iostream>
#include <cstring>
#include <algorithm>
#include<unordered_map>
using namespace std;
const int N=2500010;
typedef pair<int, int> PII;
unordered_map<int,PII>S;
int n,m;
int main()
{
cin>>n;
for(int c=0;c*c<=n;c++)
{
for(int d=c;c*c+d*d<=n;d++)
{
int t=c*c+d*d;
if(S.count(t)==0)S[t]={c,d};
}
}
for(int a=0;a*a<=n;a++)
{
for(int b=a;b*b+a*a<=n;b++)
{
int t=n-a*a-b*b;
if(S.count(t))//查询是否存在
{
printf("%d %d %d %d",a,b,S[t].first,S[t].second);
return 0;
}
}
}
return 0;
}