二分查找
面对整数时的万能二分(近似万能)
int binary(int n)
{
int l = 1, r = maxn, ans = 0;
while(l <= r)
{
int mid = (l + r) >> 1;
if(c[mid] > a[n]) ans = mid, l = mid + 1; //判断条件与ans记录位置因题而异
else r = mid - 1;
}
return ans;
}
砍树
当时少考虑k了,如何到最后mid<m了,应该取最小的k
(可行解、最优解)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+10;
ll a[maxn],m;
int n;
ll solve(ll l,ll r){
ll mid;
ll k=0;
ll sum=0;
while(l<=r){
sum=0;
mid=(r+l)/2;
// cout<<mid<<endl;
for(int i=1;i<=n;i++){
if(mid<a[i])
{
sum+=(a[i]-mid);
// cout<<sum<<endl;
}
}
if(sum>m){
l=mid+1;
k=mid;
// cout<<"r"<<r<<endl;
}
else if(sum<m){
r=mid-1;
// cout<<"l"<<l<<endl;
}
else{
return mid;
}
}
if(mid>m)
return mid;
else
return k;
}
int main(){
ll sum=0;
scanf("%d%lld",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
sum=max(sum,a[i]);
}
printf("%lld\n",solve(1,sum));
return 0;
}
二分答案
对有着单调性的答案进行二分,大多数情况下用于求解满足某种条件下的最大(小)值。
答案的单调性大多数情况下可以转化为一个函数,其单调性证明多种多样,如下:
移动石头的个数越多,答案越大(NOIP2015跳石头)。
前i天的条件一定比前 i + 1 天条件更容易(NOIP2012借教室)。
满足更少分配要求比满足更多的要求更容易(NOIP2010关押罪犯)。
满足更大最大值比满足更小最大值的要求更容易(NOIP2015运输计划)。
时间越长,越容易满足条件(NOIP2012疫情控制)。
解决的问题
1.求最大的最小值(NOIP2015跳石头)。
2.求最小的最大值(NOIP2010关押罪犯)。
3.求满足条件下的最小(大)值。
4.求最靠近一个值的值。
5.求最小的能满足条件的代价。
求最小值
int binary()
{
int l = 0, r = ll, mid;
while(l <r)
{
mid = (l + r) >> 1;
if(check(mid)) r = mid; //大多数题只要改改check()即可
else l = mid + 1;
}
return l;
}
跳石头
我们二分跳跃距离,然后把这个跳跃距离“认为”是最短的跳跃距离,然后去以这个距离为标准移石头。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m;
ll l;
const int maxn=5e4+10;
ll s[maxn];
bool judge(ll x){
int tot=0,now=0,i=0;
while(i<n+1){
i++;
if(s[i]-s[now]<x)//查看距离,如果比这个距离小,那么就搬走这个石头
tot++;
else //否则这块石头不用拿走,跳过去
now=i;
}
if(tot>m)
return false;
else
return true;
}
ll solve(ll l,ll r){
ll mid;
ll ans;
while(l<=r){
mid=(l+r)>>1;
if(judge(mid)){
ans=mid;
l=mid+1;//是可行解,查看是否还有更好的可行解
}
else r=mid-1;//不是可行解
}
return ans; //最优解
}
int main(){
scanf("%lld%d%d",&l,&n,&m);
for(int i=1;i<=n;i++)
scanf("%lld",&s[i]);
s[n+1]=l;
printf("%lld\n",solve(1,l));
return 0;
}
求最大值
int binary()
{
int l = 0, r = ll, mid;
while(l < r)
{
mid = (l + r + 1) >> 1;
if(check(mid)) r = mid - 1;
else l = mid;
}
return l;
}
数列分段
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
ll a[maxn],sum,sum_;
int n,m;
bool check(ll x){
int tot=0,i=0;
ll num=0;
while(i<=n){
i++;
if(a[i]+num>x)
tot++,num=a[i];
else num+=a[i];
}
if(tot<m)
return true;
else return false;
}
ll binary(ll l,ll r){
ll mid,ans;
while(l<=r){
mid=(l+r)>>1;
if(check(mid))ans=mid,r=mid-1;
else l=mid+1;
}
return ans;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
sum_=max(sum_,a[i]);
sum+=a[i];
}
printf("%lld\n",binary(sum_,sum));
return 0;
}
借教室
要考虑将什么东西二分很重要,本题是将前i天进行二分,用差分数组进行判断
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e6+10;
int n,m,s[maxn],t[maxn];
ll d[maxn],b[maxn],r[maxn],a[maxn];
bool check(int x){
memset(b,0,sizeof(b));
for(int i=1;i<=x;i++){
b[s[i]]+=d[i];
b[t[i]+1]-=d[i]; //差分数组
}
ll sum=0;
for(int i=1;i<=n;i++){
a[i]=a[i-1]+b[i]; //差分数组的前缀和等于a[i]
}
for(int i=1;i<=n;i++){
if(r[i]-a[i]<0)
return false;
}
return true;
}
int binary(int l,int r){
int mid,ans;
while(l<=r){
mid=(l+r)>>1;
if(check(mid))l=mid+1;
else
ans=mid,r=mid-1;
}
return ans;
}
int main(){
scanf("%d%d",&n,&m);//天数和订单数量
for(int i=1;i<=n;i++){
scanf("%lld",&r[i]);//借教室的数量
}
for(int i=1;i<=m;i++){
scanf("%lld%d%d",&d[i],&s[i],&t[i]);//租界的数量、借租开始与结束的天数
}
if(check(m)){
cout<<0<<endl;
return 0;
}
cout<<"-1"<<endl;
printf("%d\n",binary(1,m));
return 0;
}