整理题前,我先整理一下关于二分的基础知识
所谓二分:根据mid值和target值的对比来不断缩小区间,最终确定所求值
朴素模板为
while(l<=r)
{
if(mid>target)
r=mid-1;
else
l=mid+1;
}
对于模板中我们需要注意的有三点
1.while的循环语句中为何这样写
2.为何r和l的更新值要这样写
3.if语句中的判断条件
接下来带着问题去看二分答案的做法
首先
二分答案最经典也是根基的两个问题
1.最小值最大情况
2.最大值最小情况
这两种情况是最为明显的二分答案标志,通常数据量会给到1e5以上就连DP也无法解决。
拿第一种情况来说
我们假设最小值的最大情况就是取得了x,那么对于x+1就不满足题意,而x-1虽然满足题意但不是大情况。显然,我们取得区间时(L,R],每次二分的中心为M=(L+R+1)/2
所以对应的模板也有
最大值最小化的二分区间是左闭右开,[L,R),每次二分的中心为M=(L+R)/2。
while(l<r)
{
mid=(l+r+1)/2
if(check())
l=mid;
else
r=mid-1;
}
对应第二种情况,取值x则有x+1满足题意但是并非最小情况,x-1不满足题意,对应区间是[L,R),每次二分的中心为M=(L+R)/2。
while(l<r)
{
mid=(l+r)/2;
if(check)
r=mid;
else
l=mid+1;
}
对应的,如果我们的取值区间是闭区间,那么l=r就是我们还需要的一种情况,但是如果有一端是开口,则l<r作为while中的判断条件。
下面对应例题
1.最小值最大化经典例题
aggressive cows
对于这个check函数,我们是根据目前提供的候选x,看看是否能放置所有奶牛。
而二分答案的写法可以按照模板来,也可以这样
#include <bits/stdc++.h>
using namespace std;
long long n,c,a[100000+10];
bool check(long long mid)
{
int t=c-1,pre=0;
for(int i=1;i<=n;i++)
{
if(a[i]-a[pre]>=mid)
{
t--;
pre=i;
}
if(!t)
break;
}
if(!t)
return 1;
return 0;
}
int main()
{
int t;
for(cin>>t;t;t--)
{
cin>>n>>c;
for(int i=0;i<n;i++)
scanf("%lld",&a[i]);
sort(a,a+n);
int l=0,r=a[n-1];
int mid=(l+r)/2;
while(l<r)
{
if(check(mid))
l=mid+1;
else
r=mid-1;
mid=(l+r)/2;
}
cout<<r<<endl;
}
}
区间最大值最小化
书的复制
思路:我们对于他要求的值,我们先找出来,然后对于这个值再去模拟从后往前分配书本。
#include <bits/stdc++.h>
#define ll long long
#define fastio ios::sync_with_stdio(0);cout.tie(0);cin.tie(0);
#define freopen freopen("in.txt","r",stdin);
#define YES cout<<"YES"<<endl;
#define NO cout<<"NO"<<endl;
using namespace std;
long long n,m;
long long a[505];
int x[505],y[505]; //记录输出
bool check(int s) { //检查
int num=1,t=0;
for(int i=n;i>=1;i--) { //倒序
if(t+a[i]>s) t=0,num++; //换下一人
t+=a[i];
}
return num<=m; //人数是否足够
}
int find(int low,int high) { //二分
int mid;
while(low+1<high){
mid=low+(high-low)/2;
if(check(mid))
high=mid;
else
low=mid;
}
return high; //注意返回high
}
int main()
{
freopen
long long low=0,high=0;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
high+=a[i]; //上界为所有书的和
low=max(low,a[i]); //下界为最厚书的页数
}
int s=find(low,high); //获取最优值
int t=0,num=1;
y[1]=n;
for(int i=n;i>=1;i--)
{
if(t+a[i]>s)
{
t=0;
x[num]=i+1;
num++;
y[num]=i;
}
t+=a[i];
}
x[num]=1;
for(int i=m;i>=1;i--)
cout<<x[i]<<" "<<y[i]<<endl;
return 0;
}
二分答案确定固定花费下最大访问个数
地标访问
思路:对于所有给定的地标,我们必然是选定一个区间去访问,那么如何确定这个区间?
排序后,让初始最大值访问点为最后点,最小访问点为第一个点,二分查找。
check函数每次利用候选值枚举右端点,这样就能确定左端点,算出花费时间小于t则合格,注意算花费要分三种情况(因为分了正负方向)
#include <iostream>
#include <algorithm>
#include <stdio.h>
using namespace std;
int t,n,a[50000+10];
bool check(int x)
{
for(int r=x;r<=n;r++)
{
int l=r-x+1;
if(a[r]<=0)
{
if(-a[l]<=t)
return 1;
}
if(a[l]>=0)
{
if(a[r]<=t)
return 1;
}
if(a[l]<=0&&a[r]>=0)
{
if(min(a[r],-a[l])+a[r]-a[l]<=t)
return 1;
}
}
return 0;
}
int main()
{
//freopen("in.txt","r",stdin);
cin>>t>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
sort(a+1,a+n+1);
int l=-1,r=n+1;
while(l+1<r)
{
int mid=l+(r-l)/2;
if(check(mid))
l=mid;
else
r=mid;
}
cout<<l<<endl;
return 0;
}
二分答案优化
逛画展
思路:二分答案仍然超时,这时用到了巧妙地滚动数组的方式来解决
1.我们对于给定候选值可以选定1到mid作为第一个扫描区间,记录下每位画师的作品数,如果这个区间就满足包含所有画师作品的条件,那么直接返回这个方案
2.如果不满足,那么势必要更新,更新就是把1到mid,更新为2到mid+1,3到mid+2,一直到右边界小于n或者有满足条件的方案数出现为止
#include <bits/stdc++.h>
#define ll long long
#define fastio ios::sync_with_stdio(0);cout.tie(0);cin.tie(0);
#define freopen freopen("in.txt","r",stdin);
#define YES cout<<"YES"<<endl;
#define NO cout<<"NO"<<endl;
using namespace std;
int flag[1000000+10],n,m,a[2000+10],cnt;
int ansL,ansR;
bool check(int x)
{
cnt=0;
memset(flag,0,sizeof flag);
for (int i=1;i<=x;i++)
{
if (!flag[a[i]])
cnt++;
flag[a[i]]++;
}
if (cnt==m)
{
ansL=1;ansR=x;
return 1;
}
//先记录一个区间
//滚动向前更新区间
int y=1;
while (x<n)
{
flag[a[y]]--;
if (!flag[a[y]]) cnt--;
y++;
x++;
flag[a[x]]++;
if (flag[a[x]]==1) cnt++;
if (cnt==m)
{
ansL=y;ansR=x;
return 1;
}
}
return 0;
}
int main()
{
freopen
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%d",a+i);
int l=1,r=n;
while (l<=r)
{
int mid=(l+r)/2;
if (check(mid))
r=mid-1;
else l=mid+1;
}
printf("%d %d\n",ansL,ansR);
return 0;
}
木材加工
不是很难,,就没太仔细看,主要难点还是在check函数上
#include <bits/stdc++.h>
using namespace std;
long long n, k;
long long a[1000005];
bool f(long long x) {
long long ans = 0;
for (int i = 1; i <= n; i++) {
ans += a[i] / x;
}
return ans >= k;
}
int main() {
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> a[i];
long long l = 0, r = 100000001;
long long mid;
while (l + 1 < r) {
mid = (l + r) / 2;
if (f(mid)) l = mid;
else r = mid;
}
cout << l << endl;
return 0;
}
下内容我尚未搞得太明白
最长公共子序列
最长公共子序列,如果数据范围在1e4之内我们都可以用线性DP去做,但是这里明显数据卡到了1e5,题目标题还给我来个什么模板呵呵。
ans[i]代表构造长度为i时所需的a串的长度,line[i]代表a串中i这个数的位置。
每当读入第二个序列的数字m时,我们就在ans里用二分查找找到ans[r]<line[m]<ans[l],接着就可以将ans[l]的值降低为line[m]的值。当然,如果说ans中最大的数小于line[m],就可以给最长公共子序列长度加一
#include <bits/stdc++.h>
using namespace std;
int ans[100000+10],line[100000+10];
int main()
{
int n,m;
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%d",&m);
line[m]=i;
}
int total=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&m);
int s=line[m];
if(s>ans[total])
{
ans[++total]=s;
}
else
{
int l=0,r=total;
while(l<=r)
{
int mid=ans[(l+r)/2];
if(s>mid)
l=(l+r)/2+1;
else
r=(l+r)/2-1;
}
ans[l]=s;
}
}
cout<<total<<endl;
}
还有三个题,,都是类似的知识点不再赘述了