二分用于在一个单调或者局部单调有序数组中查找一个符合某些条件的值,时间复杂度为O(logN),优于普通查找的O(n)(真的不会tle吗)
A题:
二分模板题(个人模板,不建议参考)
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int a[N],n,_;
void solve()
{
int x;cin>>x;
int l=0,r=n;
while(l!=r-1)
{
int mid=(l+r)/2;
if(a[mid]<x)l=mid;
else r=mid;
}
if(a[r]==x)cout<<r<<' ';
else cout<<-1<<' ';
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n>>_;
for(int i=1;i<=n;i++)cin>>a[i];
while(_--)solve();
return 0;
}
B题:
直接看注释
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+10;
long long n,c,cnt;
int a[N];
int check(int x)
{
int l=0,r=n;
while(l+1!=r)//二分查找形参
{
int mid=(l+r)>>1;
if(a[mid]<x)l=mid;
else r=mid;
}
if(a[r]==x)return r;
else return false;
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);//取消同步流,不用管
cin>>n>>c;
for(int i=1;i<=n;i++)
cin>>a[i];
sort(a+1,a+n+1);//数组排序以满足二分条件
int p=1;//p作为"指针"对数组进行遍历
while(a[p]+c<=a[n]&&p<n)//第一个条件为了判断数组内是否可能存在数组A
{
int rt_p=check(a[p]+c);//rt_p为数值A的下标rt_v为a[rt_p]的值
int rt_v=a[rt_p];
long long cntf=0,cntl=0;//用于计数数组内B和A的个数
if(rt_p)
{
for(int i=rt_p;a[i]==rt_v;i++)
cntl++;
for(int i=p;a[i]==a[p];i++)
cntf++;
cnt+=cntl*cntf;//总数对+=B和A的个数的积
p+=cntf-1;
}
p++;//"指针"++
}
cout<<cnt;
return 0;
}
这题如果总数对不+=B和A的个数的积会被卡tle(别问我为什么知道)
C题:
由于巧克力切成正方形,不难得出每块巧克力能被分成(min{a,b}/x+max{a,b}/x)块边长为x的巧克力,可以算出在任意边长下能分的巧克力的块数的最大值,结合二分边长就可以求出满足条件的结果。
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
struct {
int a,b;
int min,max;
}cholen[N];//存巧克力
int n,k;
int check (int x)
{
int l=0,r=N;
while(l!=r-1)//二分边长
{
int mid=(l+r)>>1;
int sum=0;
for(int i=1;i<=n;i++)
sum+=(cholen[i].min/mid)*(cholen[i].max/mid);
//遍历巧克力得出边长x下能分的巧克力的最大值
if(sum>=x)l=mid;
else r=mid;
}
return l;
}
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
{
int a,b;cin>>a>>b;
cholen[i].a=a;
cholen[i].b=b;
cholen[i].min=min(a,b);
cholen[i].max=max(a,b);
}
cout<<check(k);
return 0;
}
D道:
二分思路和C题差不多(注意考虑起点和终点的石头),二分每次跳的最近距离mid,check函数判断最近距离为mid时下至少移除多少石头(石头间距离小于mid时移除下一个石头,当前位置不变)
代码有点臭(慎看)
#include <bits/stdc++.h>
using namespace std;
int a[50010];
int L,N,M;
bool check(int mid)
{
int cnt=0,now=1;//now为当前所在石头
for(int i=2;i<=N+2;i++)
if(a[i]-a[now]<mid)
cnt++;//跳跃距离小于mid时cnt++
else now=i;//否则跳过石头
if(cnt<=M)return true;
else return false;
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>L>>N>>M;
a[1]=0,a[N+2]=L;//起点 终点石头
for(int i=2;i<=N+1;i++)
cin>>a[i];
int l=0,r=1e9;
while(l!=r-1)//二分最短跳跃距离
{
int mid=(l+r)>>1;
if(check(mid))l=mid;
else r=mid;
}
cout<<l;
return 0;
}
E题:
思路同上题,直接二分电缆长度
建议用scanf和printf(可能是我比较菜,取消同步流的cin,cout狠狠tle了)
#include <cstdio>
const int N=1e4+10;
int n,k;
int a[N];
bool check(int mid)
{
long long sum=0;
for(int i=1;i<=n;i++)
sum+=a[i]/mid;
if(sum>=k) return true;
else return false;
}
int main()
{
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++)
{
double temp;scanf("%lf",&temp);//将电缆单位换为cm并转化为int
a[i]=(int)(temp*100);
}
int l=0,r=1e7+10;//1e2km是1e7cm
while(l+1!=r)//二分电缆长度
{
int mid=(l+r)>>1;
if(check(mid))
l=mid;
else
r=mid;
}
printf("%.2f",l/100.0);
}
F题:
对于任意fajomonths的预算,可以计算出该预算下需要几个fajomonths。二分一个fajomonths的预算,就可以找到fajomonths==m且fajomonths最小的值。
注:1 ≤ N ≤ 100,000
#include <cstdio>
const int N=1e6+10;
int a[N],n,m;
bool check(int max)
{
int value=0,cnt=1;
for(int i=0;i<n;i++)//算出该预算下需要多少个fajomonths
{
if(a[i]>max)return true;//单个月花费大于预算
if(value+a[i]<=max)//一个fajomonths内加上a[i]仍未超预算
{
value+=a[i];
continue;
}
else//fajomonths加上a[i]超预算,a[i]装在新的fajomonths,++cnt;
{
++cnt;
value=a[i];
}
}
if(cnt<=m)return false;
else return true;
}
int main()
{
scanf("%d %d",&n, &m);
for(int i=0;i<n;i++)scanf("%d",&a[i]);
int l=0,r=1e9;
while(l!=r-1)//二分预算
{
int mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%d",r);
return 0;
}
G题:
三分(模板题)(个人模板,不建议参考)
#include <bits/stdc++.h>
using namespace std;
const int N=18;
double a[N];
int n;
double l,r;
double f(double x)
{
double sum=0,temp=1;
for(int i=0;i<=n;i++)
sum+=pow(x,i)*a[i];
return sum;
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);
cin>>n>>l>>r;
for(int i=n;i>=0;i--)
cin>>a[i];
double lmid,rmid;
while(fabs(l-r)>1e-8)
{
lmid=l+(r-l)/3,rmid=l+(r-l)/3*2;
if(f(lmid)<f(rmid))l=lmid;
else r=rmid;
}
printf("%.5f",l);
return 0;
}
顺便找个好心人帮某菜狗debug()
//菜狗的代码
#include <bits/stdc++.h>
using i64 = long long;
const double eps = 1e-6;
int n;
double a[20];
double judge(double x) {
double res = 0;
for (int i = 0; i < n; i++) {
res = res * x + x * a[i];
}
return res;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
double l, r;
std::cin >> n >> l >> r;
for (int i = 0; i < n; i++) {
std::cin >> a[i];
}
while (l + eps <= r) {
double midl = (r - l) / 3 + l;
double midr = r - (r - l) / 3;
if (judge(midl) <= judge(midr)) {
l = midl;
} else {
r = midr;
}
}
std::cout << l << "\n";
}
H题:
思路不难,但要注意精度(我double死活过不了,坐等佬教)
#include <cstdio>
#include <cmath>
typedef long double ld;
ld a,b,ans;
ld f(long long time)
{
return (time-1)*b+a/sqrt(time);
}
int main()
{
scanf("%Lf %Lf",&a,&b);
long long l=0,r=1e18+1;
while(l<=r)
{
long long mid=(l+r)>>1;
if(f(mid+1)>=f(mid)) r=mid,ans=f(mid);
if(f(mid)>=f(mid-1)) r=mid-1,ans=f(mid);
else l=mid+1;
}
printf("%.10Lf",ans);
return 0;
}
I题:
思路不难,直接分别二分求出答案x和y的值。(记得清空缓冲区,否则喜提tle)
#include <bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int n,xl=1,yl=1,xr,yr;
bool checkx(int mid)
{
cout<<"? "<<yl<<' '<<yr<<' '<<xl<<' '<<mid<<endl;
int num;cin>>num;
if((mid-xl+1)>num)return true;//该范围的列数小于num即棋子在[xl,mid]内
else return false;
}
bool checky(int mid)
{
cout<<"? "<<yl<<' '<<mid<<' '<<xl<<' '<<xr<<endl;
int num;cin>>num;
if((mid-yl+1)>num)return true;//该范围的行数小于num即棋子在[yl,mid]内
else return false;
}
int main()
{
cin>>n;xr=n,yr=n;
while(yl<yr-1)//二分y的值
{
int mid=(yl+yr)>>1;
if(checky(mid))yr=mid;
else yl=mid+1;
}
int ansy,ansx;
if(checky(yl))ansy=yl;//我的写法需要判断yl和yr
else ansy=yr;
xl=1,yl=1,xr=n,yr=n;//记得重置
while(xl<xr-1)//二分x的值
{
int mid=(xl+xr)>>1;
if(checkx(mid))xr=mid;
else xl=mid+1;
}
if(checkx(xl))ansx=xl;//我的写法需要判断xl和xr
else ansx=xr;
printf("! %d %d",ansy,ansx);
return 0;
}
题单没有特别难的题,思路都挺简单的,但是二分和check函数的细节如果没考虑好,就容易喜提Wonderful Answer。