二分题目最全总结(持续更新)

一、概述
     二分算法是一种非常基础的算法,但是有时隐蔽性较高,或者和其他算法联用,在noip范围内是一个比较大的考点。
     对于以下简单题目,不在赘述。猜数字,单调函数找零点,快速排序 ,找第K大数,二分查找。
二、例题
     

2178 -- 【USACO 2008 January Silver】架设电话线
Description
Input
Output
Sample Input
5 7 1
1 2 5
3 1 4
2 4 8
3 2 3
5 2 9
3 4 7
4 5 6
Sample Output
4
Hint
【数据规模】
1 ≤ N ≤ 1000

我们首先来看这样一道题目,给定一张图,可以将其中一些边的权值赋成0,,问1—n的路上的最大权值最小是多少?
旁边的学神说第一眼是分层图,真的需要这样吗?
我们可以注意到提炼的题目大意中有一个关键词语 最大权值最小 ,这句话几乎可以说是二分的标志了(也可以是最小值最大),于是我们可以将该问题进行如下转化,
先确定一个长度下限m,大于m的将为免费的一条边.这样对给出的地图中,若某条边小于m,则赋值为0,否则赋值为1。求1到N的最短路即可.如果最短路≤电信公司提供的免费数目,说明m可行,接下来二分枚举比m小的值,反之二分枚举比m大的值。
代码在下面:
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
struct nodee{int fr,to,val;
}e[20005];
struct node{int to,val,nxt;
}w[20005];
int h[1005],cnt=0,m,k,d[1005],vis[1005],n;
void add(int x,int y,int z)
{
	cnt++;w[cnt].to=y;w[cnt].nxt=h[x];w[cnt].val=z;h[x]=cnt;
}
queue<int>q;
int check(int x)
{
	cnt=0;
	memset(h,0,sizeof(h));
	for(int i=1;i<=2*m;i++)w[i].val=0,w[i].nxt=0,w[i].to=0;
	for(int i=1;i<=m;i++)
	{
		if(e[i].val<x)
		  add(e[i].fr,e[i].to,0),
		  add(e[i].to,e[i].fr,0);
		else
		  add(e[i].fr,e[i].to,1),
		  add(e[i].to,e[i].fr,1);
	}
	memset(vis,0,sizeof(0));memset(d,0x3f,sizeof(d));
	q.push(1);vis[1]=1;d[1]=0;
	while(!q.empty())
	{
		int fr=q.front();
		q.pop();vis[fr]=0;
		for(int i=h[fr];i;i=w[i].nxt)
		{
			int j=w[i].to;
			if(d[j]>d[fr]+w[i].val)
			{
				d[j]=d[fr]+w[i].val;
				if(!vis[j])
				{
					q.push(j);
					vis[j]=1;
				}
			}
		}
	}
	return d[n];
}
int main(){
	cin>>n>>m>>k;
	int l,r=0;
	for(int i=1;i<=m;i++)
	{
		cin>>e[i].fr>>e[i].to>>e[i].val;
		r=max(r,e[i].val);
	}
	l=1;
	int tmp=r;
	while(l<=r)
	{
		
		int mid=(l+r)>>1;
		if(check(mid)<=k)r=mid-1;
		else l=mid+1;
	}
	if(tmp==r)cout<<-1;
	else cout<<l-1;
	return 0;
}


是不是很简单呢?这类题目还有很多
比如下面这道

TYVJ1359 收入计划
描述
    高考结束后,同学们大都找到了一份临时工作,渴望挣得一些零用钱。从今天起,Matrix67将连续工作N天(1<=N<=100 000)。每一天末他可以领取当天及前面若干天里没有领取的工资,但他总共只有M(1<=M<=N)次领取工资的机会。Matrix67已经知道了在接下来的这N天里每一天他可以赚多少钱。为了避免自己滥用零花钱,他希望知道如何安排领取工资的时间才能使得领到工资最多的那一次工资数额最小。注意Matrix67必须恰好领工资M次,且需要将所有的工资全部领走(即最后一天末需要领一次工资)。
输入格式
    第一行输入两个用空格隔开的正整数N和M
    以下N行每行一个不超过10000正整数,依次表示每一天的薪水。
输出格式
   输出领取到的工资的最大值最小是多少。
测试样例1
输入
7 5
100
400
300
100
500
101
400
输出
500
备注
【样例说明】
    采取下面的方案可以使每次领到的工资不会多于500。这个答案不能再少了。
100 400   300 100   500   101   400   每一天的薪水
<------1 <-------2 <---3 <---4 <---5  领取工资的时间
  500       400     500   101   400   领取到的工资

一道典型的二分,代码我就不发啦,二分工资数额即可。


洛谷P1525 BSOJ2809 -- 【NOIP 2010提高】关押罪犯
Description
  S城现有两座监狱,一共关押着N名罪犯,编号分别为1~N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨 气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为 c 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为c的冲突事件。
  每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到S城Z市长那里。公务繁忙的Z市长只会去看列表中的第一个事件的影响力, 如果影响很坏,他就会考虑撤换警察局长。
  在详细考察了N名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。那么,应如何分配罪犯,才能使Z市长看到的那个冲突事件的影响力最小?这个最小值是多少?
Input
  输入文件名为 prison.in。输入文件的每行中两个数之间用一个空格隔开。
  第一行为两个正整数N和M,分别表示罪犯的数目以及存在仇恨的罪犯对数。
  接下来的M行每行为三个正整数 aj,bj,cj,表示aj号和bj号罪犯之间存在仇恨,其怨气值为cj。数据保证1≤aj
Output
    输出文件prison.out共1行,为Z市长看到的那个冲突事件的影响力。如果本年内监狱 中未发生任何冲突事件,请输出0。
Sample Input
4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884
Sample Output
3512
Hint
【输入输出样例说明】
  罪犯之间的怨气值如下面左图所示,右图所示为罪犯的分配方法,市长看到的冲突事件 影响力是3512(由2号和3号罪犯引发)。其他任何分法都不会比这个分法更优。
【数据范围】
  对于30%的数据有N≤15。
  对于70%的数据有N≤2000,M≤50000。
  对于100%的数据有N≤20000,M≤100000。

这道题目解法比较多。 三种方法,一种是并查集,另一种是排序+二分+二分图判定,或者用最大生成树
二分其实也是很好想的,网上题解很多,不多说了,说实话并查集好做。


BSOJ3533 -- 【NOIP 2012提高】借教室
Description
  在大学期间,经常需要租借教室。大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室。教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样。
  面对海量租借教室的信息,我们自然希望编程解决这个问题。
  我们需要处理接下来n天的借教室信息,其中第i天学校有ri个教室可供租借。共有m份订单,每份订单用三个正整数描述,分别为dj, sj, tj,表示某租借者需要从第sj天到第tj天租借教室(包括第sj天和第tj天),每天需要租借dj个教室。
  我们假定,租借者对教室的大小、地点没有要求。即对于每份订单,我们只需要每天提供dj个教室,而它们具体是哪些教室,每天是否是相同的教室则不用考虑。
  借教室的原则是先到先得,也就是说我们要按照订单的先后顺序依次为每份订单分配教室。如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配,通知当前申请人修改订单。这里的无法满足指从第sj天到第tj天中有至少一天剩余的教室数量不足dj个。
  现在我们需要知道,是否会有订单无法完全满足。如果有,需要通知哪一个申请人修改订单。
Input
  第一行包含两个正整数n, m,表示天数和订单的数量。
  第二行包含n个正整数,其中第i个数为ri,表示第i天可用于租借的教室数量。
  接下来有m行,每行包含三个正整数dj, sj, tj,表示租借的数量,租借开始、结束分别在第几天。
  每行相邻的两个数之间均用一个空格隔开。天数与订单均用从1开始的整数编号。
Output
  如果所有订单均可满足,则输出只有一行,包含一个整数 0。否则(订单无法完全满足)输出两行,第一行输出一个负整数-1,第二行输出需要修改订单的申请人编号。
Sample Input
4 3
2 5 4 3
2 1 3
3 2 4
4 2 4
Sample Output

-1 2
【输入输出样例说明】 第 1 份订单满足后,4 天剩余的教室数分别为 0,3,2,3。 第 2 份订单要求第 2 天到第 4 天每天提供 3 个教室,而第 3 天剩余的教室数为 2,因此无法满足。分配停止,通知第2 个申请人修改订单。

Hint

【数据范围】
  对于10%的数据,有1 ≤ n, m ≤ 10;
  对于30%的数据,有1 ≤ n, m ≤ 1000;
  对于70%的数据,有1 ≤ n, m ≤ 10^5;
  对于100%的数据,有1 ≤ n, m ≤ 10^6, 0 ≤ ri, dj ≤ 10^9, 1 ≤ sj ≤ tj ≤ n。

这道题难道要上线段树吗(还A不了。。。),对于这种类似的题目,可以将线段树转化成差分序列,那么二分订单数(哪一个无法满足就非常容易想到了)。
有代码了,开心吗(嘿嘿嘿)
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cmath>
#include<cstdio>
using namespace std;
struct node{int l,r,c;
}w[1000005];
int num[1000005],n,r[1000005],m;
int judge(int x)
{
	memset(num,0,sizeof(num));
	for(int i=1;i<=x;i++)
	{
		num[w[i].l]+=w[i].c;
		num[w[i].r+1]-=w[i].c;
	}
	int sum=0;
	for(int i=1;i<=n;i++)
	{
		sum+=num[i];
		if(sum>r[i])return 0;
	}
	return 1;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	  scanf("%d",&r[i]);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&w[i].c,&w[i].l,&w[i].r);
	}
	int l=1,r=m+1;
	while(l<r)
	{
		int mid=(l+r)/2;
		if(!judge(mid))r=mid;
		else l=mid+1;
	}
	if(r==m+1)cout<<0;
	else cout<<-1<<"\n"<<r;
	return 0;
}
与下面这道题目类似【POJ3657】【USACO 2008 Jan Gold】 Haybale Guessing 二分答案,并查集check

总而言之,二分作为一种能够配合很多算法的基础算法,其应用是非常广的。
to be continued

  • 2
    点赞
  • 0
    评论
  • 4
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

打赏
文章很值,打赏犒劳作者一下
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页

打赏

moep0

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者