二分最化最值问题(一)
二分最化最值问题
把一个包含n个正整数的序列划分成m个连续的子序列(每个正整数恰好属于一个序列).
设i个序列的各数之和为s(i).你的任务是让所有s(i)的最大值尽量小.
这个算法很有现实意义.给你一堆乱七八糟的东西分堆,分出来的东西都不超过一个固定的数值,这是需要技术含量的喔.
1.首先确定二分的上下界,这是每次二分必须做的准备工作.在这里上限是这一堆东西的总量,下限是单个最大物品的值.
2.确定上下限之后开始二分.即判断当前分堆是否合理,再判断分堆是否合理中,主要限制因素为两个,一个是单堆的量肯定不能超过当前的mid值,另一个是分出的堆数一定不能超过题目要求的m值.
3.由判断条件即可将二分范围进行缩小,即一旦当前mid值满足分堆要求,意味着我还可以把mid值缩小(最小化),即把二分的right=mid,如果当前mid值触犯了判断条件,就把left=mid.
4.由以上二分return
的结果即为所求值.
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int a[N];
int ok(int m)
{
int sum=0,cnt=0;
for(int i=0;i<n;i++)
{
if(a[i] > m) return false;
if(sum + a[i] > m)
{
cnt++;
sum = a[i];
}
else sum+=a[i];
}
return cnt <= m;
}
int main()
{
int n,k,sum=0;
cin>>n>>k;
for(int i=0;i<n;i++)
{
cin>>a[i];
sum+=a[i]; //r
Max=max(Max,a[i); //l
}
int l=Max,r=sum;
while(l<=r)
{
int mid=(l+r)/2;
if(ok(mid)) r=mid;
else l=mid+1;
}
cout<<l<<endl;
}
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int a[N];
int ok(int m)
{
int sum=0,cnt=0;
for(int i=0;i<n;i++)
{
if(a[i] > m) return false;
if(sum + a[i] > m)
{
cnt++;
sum = a[i];
}
else sum+=a[i];
}
return cnt <= m;
}
int main()
{
int n,k,sum=0;
cin>>n>>k;
for(int i=0;i<n;i++)
{
cin>>a[i];
sum+=a[i]; //r
Max=max(Max,a[i); //l
}
int l=Max,r=sum;
while(l<=r)
{
int mid=(l+r)/2;
if(ok(mid)) r=mid;
else l=mid+1;
}
cout<<l<<endl;
}
最小化最大值
和最大化最小值问题正好相反,同样用二分解决,差别主要在判断条件.
1.最大化最小值(兼济天下)
相当于把n个东西分给m个人,使得每个人至少拿x个,那么每个人拿够了就走,给后面的人多留一点,只要能分够>=m个人就是true,多的全扔给最后一个人就是了.
2.最小化最大值(独善其身)
相当于n个东西分给m个人,每个人至多拿x个 ,那么每个人尽可能拿多一点,给后面的人少留一点,只要能使<=m个人分完n个东西就是true,之后每个人随便拿一点给没有拿到的人就是了.
注意:
1.关于初始值,有些题目直接给前缀和,那样最小值的最大值就是平均值,最大值的最小值也是平均值 对于可以计算sum的题有:
2.最大化最小值:
x为单点最小值,y为平均值.
3.最小化最大值:
x为平均值与单点最大值两者中大的那一个,y为sum.其实直接令x=0,y=0x7fffffff即可,不过循环32次而已...