1. 程式人生 > >ACM_二分(POJ1064)

ACM_二分(POJ1064)

1.二分法求解問題的這種思想其實十分的普遍。回憶一下我們在求一個方程的近似根的時候其實使用的就是二分的辦法,當然這種求近似根的方法是有一定侷限的,因為這樣無法求不變號的零點,也就說哪種曲線剛剛好與座標軸相切的零點我們通過普通的二分是無法求得的。但是,二分求近似根這種方法體現出來的二分思想其實是可以被用來解決很多的問題。

2.二分查詢。如果我們在一個有序的數組裡查詢不大於某個數最大的下標,其實我們就可以通過二分來做,具體的程式碼在這裡就不寫了,思想和求根一樣,不斷的二分查詢區間,直到找到為止。這樣其實我們把複雜度下降到logn。

3.繩子的切割問題這個問題的大概意思就是給你一些繩子,和一個整數K,讓你把這些繩子切割成K根長度相同的繩子並且使得這K根繩子儘可能的長。拿到這個問題,我們該如何分析呢?我們對最終的長度分析,設每一根根繩子的長度為x,那麼我們可以知道的是,在長度為x的時候我們可以切出來的就是\sum L[i]/x

,設這個數字就是num。也就是說:num=\sum L[i]/x。如果這個num>=K的,這代表什麼意思呢?也就是說,現在K根長度為x,是可以切出來的,並且切出來之後的數量還是大於K的,說明這個最優解一定是大於x的,如果num<x呢,那就說明每一段的長度為x是不可行的,因為切出來的數量少了。當然我們可以遍歷整個可能的x,一一驗證,也就是列舉(或者說是暴力),但是這樣的複雜度是遠遠不行的,觀察到我們需要的長度是排列好的,也就誰有序的,而且解的排列也是排列在一條數軸上(注意這裡面的含義,這是一個問題可不可以二分求解的一個重要的特診)那麼我們就可以二分整個區間求解。區間的下限當然是很好解決了,肯定是0嘛,上限呢?根據資料範圍我們也可以很簡單的確定下來。但是一般1要把這個二分的區間拉大一點,當然這裡的拉大是適當的拉大。這樣我們每次驗證區間的重點,如果可行,更新左端點,不可行更新右端點。這樣就可以很快的實現:
 

#include<iostream>
#include<cstdio>
#include<stdio.h>
#include<math.h>
#pragma warning(disable:4996)
using namespace std;
const int maxn = 1e4 + 10;
int N, K;
double len[maxn];
#define INF 2e5+10;
bool OK(double c)
{
    int num = 0;
    for (int i = 0; i < N; i++)
    {
        num += (int)(len[i] / c);
    }
    return num >= K;
}
void solve()
{
    double l = 0;
    double r = INF;
    for (int i = 0; i < 100; i++)
    {
        double mid = (l + r) / 2;
        if (OK(mid))l = mid;
        else r = mid;
    }
    printf("%0.2f\n", floor(r * 100) / 100);
}
int main()
{
    while (cin >> N >> K)
    {
        for (int i(0); i < N; i++)
            scanf("%lf", len + i);
        solve();
    }
    return 0;
}