YbtOJ 「基础算法」第3章 二分算法
作者:互联网
例题1.数列分段
二分每段和的最大值。check 时从左往右扫,如果当前段的和大于限制则新开一段。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m,a[N];
int maxn,s;
int check(int x)
{
int cnt=1,sum=0;
for(int i=1;i<=n;i++)
{
if(sum+a[i]>x) cnt++,sum=a[i];
else sum+=a[i];
}
//cout<<x<<" "<<cnt<<endl;
return cnt;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),maxn=max(maxn,a[i]),s+=a[i];
int l=maxn,r=s;
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid)<=m) r=mid;
else l=mid+1;
}
cout<<l<<endl;
return 0;
}
/*
6 3
4 2 4 5 1 1
*/
例题2.防具布置
因为中间只有一个奇数,考虑求前缀和。则该奇数位以前的前缀和都是偶数,以后的都是奇数。二分这个分界点即可,注意判无解。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5;
int T,n,s[N],e[N],d[N];
int check(int x)
{
int sum=0;
for(int i=1;i<=n;i++)
{
if(s[i]>x) continue;
sum+=(min(e[i],x)-s[i])/d[i]+1;
}
return sum;
}
signed main()
{
scanf("%lld",&T);
while(T--)
{
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%lld%lld%lld",&s[i],&e[i],&d[i]);
long long l=1,r=(long long)(1ll<<31ll)-1ll;
if((check(r)&1)==0) {cout<<"There's no weakness."<<endl;continue;}
//cout<<l<<" "<<r<<endl;
while(l<r)
{
int mid=(l+r)>>1;
//cout<<mid<<" "<<check(mid)<<endl;
if((check(mid)&1)==0) l=mid+1;
else r=mid;
}
cout<<l<<" "<<check(l)-check(l-1)<<endl;
}
return 0;
}
例题3.最大均值
实数域上二分平均值。
check 时将区间求和转化为前缀和的形式,则对于每一个 \(i\),找到 \([1,i-l+1]\) 中最小的 \(j\) 并逐一判断能否满足条件。
\(j\) 的区间每次加 1,无需循环,开变量记录当前最小值即可。
code
#include<bits/stdc++.h>
using namespace std;
typedef double db;
const int N=1e5+5;
const db eps=1e-5;
int n,l,a[N],maxn;
db b[N];
bool check(db x)
{
db minn=1e9;
for(int i=1;i<=n;i++) b[i]=1.0*a[i]-x;
//for(int i=1;i<=n;i++) cout<<b[i]<<" ";
//cout<<endl;
for(int i=1;i<=n;i++) b[i]=b[i-1]+b[i];
for(int i=l;i<=n;i++)
{
minn=min(minn,b[i-l]);
//cout<<i<<" "<<b[i]<<" "<<minn<<endl;
if(b[i]-minn>=-eps) {return 1;}
}
return 0;
}
int main()
{
scanf("%d%d",&n,&l);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),maxn=max(maxn,a[i]);
db l=-1e9,r=maxn;
while(r-l>eps)
{
db mid=(l+r)/2.0;
if(check(mid)) l=mid;
else r=mid;
}
printf("%.0lf\n",l*1000.0);
return 0;
}
1.喂养宠物
二分养的兔子数量,求出对应每个兔子需要的食物数量。
将兔子按需要食物多少排序,从小往大买兔子,根据需要食物总和判断方案是否合法。
code
#include<bits/stdc++.h>
using namespace std;
const int N=55;
int n,t,h[N],g[N],c[N];
int minn=2e9;
int check(int x)
{
for(int i=1;i<=n;i++) c[i]=h[i]+g[i]*(x-1);
sort(c+1,c+n+1);
int sum=0;
for(int i=1;i<=x;i++) sum+=c[i];
return sum;
}
int main()
{
scanf("%d%d",&n,&t);
for(int i=1;i<=n;i++) scanf("%d",&h[i]),minn=min(minn,h[i]);
for(int i=1;i<=n;i++) scanf("%d",&g[i]);
if(minn>t) {cout<<"0"<<endl;return 0;}
int l=1,r=n;
while(l<r)
{
int mid=(l+r+1)>>1;
if(check(mid)<=t) l=mid;
else r=mid-1;
}
cout<<l<<endl;
return 0;
}
2.最小时间
两种情况:
- 答案单调递减:判断 \(0\) 是否可行,因为数据保证有解,所以不可行就一定是第二种情况。
- 答案单调递增:二分答案,从大到小排序取数。使用
nth_element
把复杂度降低一个 log。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,m,s,k[N],b[N],c[N];
bool check(int x)
{
for(int i=1;i<=n;i++) c[i]=k[i]*x+b[i];
nth_element(c+1,c+m,c+n+1,greater<int>());
int sum=0;
for(int i=1;i<=m;i++)
{
if(c[i]<0)continue;
sum+=c[i];
if(sum>=s) return 1;
}
return 0;
}
signed main()
{
scanf("%lld%lld%lld",&n,&m,&s);
for(int i=1;i<=n;i++) scanf("%lld%lld",&k[i],&b[i]);
int l=1,r=1e9;
if(check(0)) {cout<<"0"<<endl;return 0;}
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<l<<endl;
return 0;
}
3.攻击法坛
留坑待填。
4.跳石头
二分最短跳跃距离,把所有距离小于 mid 的石头移走,统计移走石头数。
code
#include<bits/stdc++.h>
using namespace std;
int s,n,m,a[500005];
int check(int x)
{
int now=0,ans=0;
for(int i=1;i<=n+1;i++)
{
if(a[i]-a[now]<x) ans++;
else now=i;
}
return ans;
}
int main()
{
scanf("%d%d%d",&s,&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
a[n+1]=s;
int l=1,r=s,mid;
while(l<r)
{
mid=(l+r+1)>>1;
if(check(mid)>m) r=mid-1;
else l=mid;
}
cout<<r<<endl;
return 0;
}
标签:二分,code,const,int,YbtOJ,sum,mid,算法,check 来源: https://www.cnblogs.com/ying-xue/p/16584995.html