1. 程式人生 > >二分題目最全總結(持續更新)

二分題目最全總結(持續更新)

一、概述      二分演算法是一種非常基礎的演算法,但是有時隱蔽性較高,或者和其他演算法聯用,在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