二分搜索

對《挑戰程序設計競賽》的一個記錄


第三章  出類拔萃——中級篇


3.1 二分搜索


(1) 從有序數組中查找某個值

這個是最常見的二分搜索,在這就不多說了


(2)假定一個解並判斷是否可行

之前不知道原來二分有那麼多用處,往下看

poj 1064

有N條繩子,他們的長度分別爲Li,如果從他們中切割出K條相同的繩子的話,這K條繩子每段最長能有多長?答案保留到小數點後2位。

已知:

1≤N≤10000

1≤K≤10000

1≤Li≤100000


sample input 

N = 4

K = 11

L = {8.02,7.43,4.57,5.39}

sample output 

2.00(每條繩子可以得到4條,3條,2條,2條,總計11條)


二分搜索模型的建立,令條件C(x):=可以得到K條長度爲x的繩子

我們要二分的是繩子的長度,已知最小長度爲0(l = 0),最大長度爲最長繩子的長度(r = INF),當二分到某個值時,如果能得到的繩子數>K,表明當前值小了,還可以切更大的

這題要注意的是最後輸出時,長度不要四捨五入後的值,例如2,005我要的是2.00而不是2.01,如果直接printf("%.2lf",ans),會自動四捨五入的。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <string>

#define sf scanf
#define pf printf
#define eps 1e-10
using namespace std;

const int Maxn = 10010;

double len[Maxn];
int n,k;
int getnum(double mid)
{
    int num  = 0;
    for(int i = 0;i < n;i ++)
        num += (int)(len[i]/mid);
    return num;
}
double solve(double r)
{
    double l = 0;
    while(l - r < -eps)
    {
        double mid = (l + r) / 2;
        if(getnum(mid) < k)
            r = mid;
        else
            l = mid;
    }
    return r;
}
int main()
{
    while(~sf("%d%d",&n,&k))
    {
        double Max = 0;
        for(int i = 0;i < n;i ++)
        {
            sf("%lf",&len[i]);
            if(len[i] > Max)
                Max = len[i];
        }

        double ans = solve(Max);
        pf("%.2f\n",floor(ans * 100) / 100);
    }
    return 0;
}

(3) 最大化最小值

poj 2456  Aggressive cows

農夫約翰搭了一間有N見牛舍的小屋。牛舍排在一條線上,第i號牛舍在xi的位置,但是他的M頭牛對小屋很不滿意,因此經常相互攻擊,約翰爲了防止牛之間互相傷害,因此決定把每頭牛放在離其他牛儘可能遠的牛舍,也就是最大化最近的兩頭牛之間的距離。

已知:

2≤N≤100000

2≤M≤N

0≤Xi≤10^9


sample input

N = 5

M = 3

x = {1,2,8,4,9}

sample output

3(在位置1,4,8的牛舍中放入三頭牛)


這道題其實跟上一題其實很像,我們可以定義C(d):=可以安排牛的位置使得任意牛的間距不小於d

首先按牛舍的距離排序

二分到某個d值後,從第一個牛舍開始放牛,找到一個牛舍使得它與之前放牛的牛舍距離≥d,再放入一頭牛,依次計算,得到一共能放k頭牛,如果k>M,說明二分的d值太小,還可以繼續擴大。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <string>

#define sf scanf
#define pf printf

using namespace std;

const int Maxn = 100010;
int n,k;
int x[Maxn];
int getnum(int mid)
{
    int num = 1;
    int f = x[0];
    for(int i = 1;i < n;i ++)
    {
        if(x[i] - f >= mid)
        {
            num ++;
            f = x[i];
        }
    }
    return num;
}
int solve(int l,int r)
{
    while(l <= r)
    {
        int mid = (l + r) / 2;
        if(getnum(mid) < k)
            r = mid - 1;
        else
            l = mid + 1;
    }
    return r;
}
int main()
{

    while(~sf("%d%d",&n,&k))
    {
        for(int i = 0;i < n;i ++)
            sf("%d",&x[i]);
        sort(x,x + n);
        pf("%d\n",solve(0,x[n - 1] - x[0]));
    }
    return 0;
}

(4) 最大化平均值

有n個物品的重量和價值分別問wi和vi,從中選出k個物品使得單位重量的價值最大

已知:

1≤k≤n≤10^5

1≤wi,vi≤10^6

sample input

n = 3

k = 2

{w,v} = {{2,2},{5,3},{2,1}}

sample output

0.75(如果選擇0號和2號,平均價值爲(2 + 1)/(2 + 2) = 0.75)


這題的想法比前兩個難一點,首先想到的就是對平均值進行二分枚舉,但是枚舉到值後,怎麼去確定到底是由哪些物品構成了這個平均值。

換個思路,如果是按貪心的方法,把物品按單位價值進行排序,從大到小進行貪心地進行選取,但是這個方法的得到的結果是5/7 = 0.714,那到底該如何求解呢?

還是二分,我們定義條件C(x):=可以選擇使得單位重量的價值不小於x。我們選擇某些物品的集合S,使得其單位重量的價值爲


因此判斷是否存在S滿足下面的條件:

 即

因此對vi-x*wi進行貪心的選取就好了,按vi-x*wi從大到小排,如果前k個值使得不等式成立,則這個集合S是符合條件的,可以繼續擴大x的範圍。


poj 3111 代碼如下:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <string>

#define sf scanf
#define pf printf
#define eps 1e-7
using namespace std;

const int Maxn = 100010;
int n,k;
int ans[Maxn];
struct node
{
	double w,v;
	double perval;
	int p;
};
node jew[Maxn];

double cmp(node x,node y)
{
	return x.perval > y.perval;
}
int getnum(double mid)
{
	for(int i = 0;i < n;i ++)
		jew[i].perval = jew[i].v - jew[i].w * mid;
	sort(jew,jew + n,cmp);		
	double num = 0;
	for(int i = 0;i < k;i ++)
		num += jew[i].perval;
	return num >= 0;
}
void solve(double l,double r)
{
	while(l - r < -eps)
	{
		double mid = (l + r) / 2;
		if(getnum(mid))
		{
			l = mid;
			for(int i = 0;i < k;i ++)
				ans[i] = jew[i].p;
		}
			
		else
	 		r = mid;
	}
}
int main()
{

	while(~sf("%d%d",&n,&k))
	{
		double Max = 0;
		for(int i = 0;i < n;i ++)
		{
			sf("%lf%lf",&jew[i].v,&jew[i].w);
			jew[i].p = i + 1;
			if(jew[i].v / jew[i].w > Max)
				Max = jew[i].v / jew[i].w;
			ans[i] = i + 1;
		}
		solve(0,Max);
		sort(ans,ans + k);
		pf("%d",ans[0]);
		for(int i = 1;i < k;i ++)
			pf(" %d",ans[i]);
		pf("\n");
	}
	return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章