1. 程式人生 > >貪心+二分解決最大值最小、最小值最大問題

貪心+二分解決最大值最小、最小值最大問題

在刷題時,總會遇到求最大值最小,最小值最大問題,也許它會暗喻是這樣的一個問題。對於這樣的一個問題,你會發現用dp和列舉都會超時超記憶體,或者說很麻煩,所以這是一個比較簡單的解題方式。

二分逼近思想

對於難以直接確定解的問題,採取二分列舉+檢驗的思想.

已知解為x,驗證x是否滿足要求.

如果答案具有特定的範圍,並且驗證答案是否成立的函式具有單調性。則可以在範圍內對答案進行二分驗證,從而快速確定答案。

 對於答案判斷:

在二分答案的時候需要判斷,從而確定下一個範圍。

可以用一個bool Check(x)函式來判斷,返回true表示滿足,返回false表示不滿足.

可以類比數學函式

f(x)>=0f(x)<0來理解.

根據具體問題寫出相應的Check函式往往就是解決問題的關鍵點。

下面舉例一些問題:

poj2456

Aggressive cows

Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 13139 Accepted: 6399

Description

Farmer John has built a new long barn, with N (2 <= N <= 100,000) stalls. The stalls are located along a straight line at positions x1,...,xN (0 <= xi <= 1,000,000,000). 

His C (2 <= C <= N) cows don't like this barn layout and become aggressive towards each other once put into a stall. To prevent the cows from hurting each other, FJ want to assign the cows to the stalls, such that the minimum distance between any two of them is as large as possible. What is the largest minimum distance?

Input

* Line 1: Two space-separated integers: N and C 

* Lines 2..N+1: Line i+1 contains an integer stall location, xi

Output

* Line 1: One integer: the largest minimum distance

Sample Input

5 3
1
2
8
4
9

Sample Output

3

Hint

OUTPUT DETAILS: 

FJ can put his 3 cows in the stalls at positions 1, 4 and 8, resulting in a minimum distance of 3. 

Huge input data,scanf is recommended.

Source

2.題意概述:

農夫有c頭牛,n個隔間,c頭牛很躁動,很容易相互打架,因此農夫想把它們分得越遠越好,要你分配隔間使得相鄰兩頭牛的距離越遠越好,問你這c頭牛分割的最小距離的最大值。

3.解題思路: 

先對隔間的座標排序,對於牛,最小距離是0,最大距離不會超過兩端兩頭牛的距離值,因此二分地查詢分割距離的最大值,每次mid都judge一次,judge的時候貪心地放置牛,保證前i頭牛是符合這樣分割標準的。

AC程式碼:

#include<iostream>
#include<algorithm>
using namespace std;
	int n,k,t;
	int a[100000];
bool fun(int d){
	int next,last=0,k=1;
	for(int i=1;i<t;i++){
		if(a[i]-a[last]>=d){
			k++;
			last=i;
		}
	}
	if(k>=n)
		return true;
	else
		return false;
}
int main(){
cin>>t;
cin>>n;
	for(int i=0;i<t;i++)
		cin>>a[i];
	sort(a,a+t);
	int l=0,r=a[t-1],mid=(l+r)/2;
	while(l<=r){
		if(fun(mid)){
			l=mid+1;
		}
		else{
			r=mid-1;	
		}
		mid=(l+r)/2;
	
	}
	cout<<r<<endl;
	return 0;
} 

poj1505 

Copying Books

Time Limit: 3000MS Memory Limit: 10000K
Total Submissions: 9632 Accepted: 3013

Description

Before the invention of book-printing, it was very hard to make a copy of a book. All the contents had to be re-written by hand by so called scribers. The scriber had been given a book and after several months he finished its copy. One of the most famous scribers lived in the 15th century and his name was Xaverius Endricus Remius Ontius Xendrianus (Xerox). Anyway, the work was very annoying and boring. And the only way to speed it up was to hire more scribers. 

Once upon a time, there was a theater ensemble that wanted to play famous Antique Tragedies. The scripts of these plays were divided into many books and actors needed more copies of them, of course. So they hired many scribers to make copies of these books. Imagine you have m books (numbered 1, 2 ... m) that may have different number of pages (p1, p2 ... pm) and you want to make one copy of each of them. Your task is to divide these books among k scribes, k <= m. Each book can be assigned to a single scriber only, and every scriber must get a continuous sequence of books. That means, there exists an increasing succession of numbers 0 = b0 < b1 < b2, ... < bk-1 <= bk = m such that i-th scriber gets a sequence of books with numbers between bi-1+1 and bi. The time needed to make a copy of all the books is determined by the scriber who was assigned the most work. Therefore, our goal is to minimize the maximum number of pages assigned to a single scriber. Your task is to find the optimal assignment. 

Input

The input consists of N cases. The first line of the input contains only positive integer N. Then follow the cases. Each case consists of exactly two lines. At the first line, there are two integers m and k, 1 <= k <= m <= 500. At the second line, there are integers p1, p2, ... pm separated by spaces. All these values are positive and less than 10000000.

Output

For each case, print exactly one line. The line must contain the input succession p1, p2, ... pm divided into exactly k parts such that the maximum sum of a single part should be as small as possible. Use the slash character ('/') to separate the parts. There must be exactly one space character between any two successive numbers and between the number and the slash. 

If there is more than one solution, print the one that minimizes the work assigned to the first scriber, then to the second scriber etc. But each scriber must be assigned at least one book.

Sample Input

2
9 3
100 200 300 400 500 600 700 800 900
5 4
100 100 100 100 100

Sample Output

100 200 300 400 500 / 600 700 / 800 900
100 / 100 / 100 / 100 100

題意:按順序給你N個數,將這N個數分成連續的M段,使得這M段每段的和中的最大值最小,輸出最小值(1<=N<=100000,1<=M<=N,每個數在1到10000之間),如果有多種可能的話,儘量在前面進行劃分。(其實第一次讀題還不知道是什麼意思,仔細琢磨才明白是這樣的一個問題)

首先以正常的思路去考慮優化這個最大值的最小化問題,好像會陷入到迴圈之中找不到解決的思路,但是我們如果採用猜測-判斷-重來的方法不斷的去嘗試二分查詢最大值的最小值,然後去判斷這個值是否合法,進一步優化這個值,就可以解決。

ac程式碼:

#include<iostream>
using namespace std;
int a[501];
int t,m,n;
int yes_no(int dis,int flag){
	int last=0,next=0,tsum=a[last],cnt=1;
	while(next<m){
		if(tsum<=dis){
			next++;
			tsum=tsum+a[next];
		}
		if(tsum>dis){
			cnt++;
			last=next;
			tsum=a[last];
		}
		
	}
//	cout<<cnt<<endl;
	if(cnt>flag)
		return 2;
	else if(cnt==flag)
		return 1;
	else
		return 0;
}
int main(){
	cin>>t;
	while(t--){
		int sum=0,ans;
		cin>>m>>n;
		for(int i=0;i<m;i++){
			cin>>a[i];
			sum+=a[i];
		}
		//cout<<sum<<endl;
		int l=0,r=sum,mid;
		//cout<<mid<<endl;	
		while(l<=r){
			mid=(l+r)>>1;
			if(yes_no(mid,n)==2){
				l=mid+1;
				//cout<<"1";
			}
			else if(yes_no(mid,n)==1){
				ans=mid;
				r=mid-1;
			}
			else{
				r=mid-1;
			//	cout<<"2";
			}
		}
		//cout<<ans<<endl;
		int tsum=0;
		int tn=n-1;
		for(int i=0;i<m;i++){
			tsum+=a[i];
			if(tsum>=ans&&tn){
				tn--;
				cout<<" /";
				tsum=a[i];
			}
			if(i==0){
				cout<<a[i];
			}
			else
				cout<<" "<<a[i];				
		}	cout<<endl;	
	}
	
} 

以上就是這兩題,是基本的貪心+二分的求法。 

注意:二分是在排序好的基礎上進行的查詢,所以,看清題目,儘量先排好序。。。