NYIST 914Yougth的最大化【二分搜索/Dinkelbach算法】


 

轉載請注明出處:http://www.cnblogs.com/KirisameMarisa/p/4187637.html 

 

題目鏈接:http://acm.nyist.net/JudgeOnline/problem.php?pid=914

問題描述:有N個物體,它們的利益用v[i]表示,代價用c[i]表示。現在要在這N個物體中選取K個物體,使得選出來的這K個物體的總利益除以總代價達到最大值。即取得最大值。

問題轉化:構造一個x[N]的數組,表示每個數取或不取的狀態,顯然每一個x[i]只有兩個取值:0和1,其中1表示取,0表示不取。則整個式子也就可以變成目標式:

值得注意的是:上式中的r是每一組x對應求得的當前答案,而我們的目的就是要找到一組x使得求出來的r得到最大值R!(這很重要!)

 


 一、二分法

進一步對式子進行處理:

簡單的一項處理:

構造一個函數:

其中F(r)在平面坐標系上體現為一條直線,每一組x[i]都分別唯一地對應一條直線,這些直線的截距均大於等於0、斜率均小於等於0。而這些直線在X軸上的截距就是這一組x求出來的r,而截距的最大值就是我們要求的R。(如下圖所示)

在X軸上面任取一個r,如果至少有一條直線的F(r)>0,那么說明了什么呢?

說明至少還有一條直線與X軸的交點在它的右邊,那么這個r一定不是最大值,真正的最大值在它的右邊。反之,如果所有的F(r)都小於0,那么真正的最大值在它的左邊

 

那么前面的結論就可以換種說法,因為我只需判斷最大的那個F(r)的正負性就行了:

隨便取一個r,如果F(r)max>0,則結果R>r,反之若F(r)max<0,則結果R<r。直到找到使F(r)max=0的r,那個r就是我們要找的結果R

顯然這是一個令人感到興奮的結論,因為我們自然而然就想到了一種方法:二分法!

至此,還有一個小小的問題沒有解決,那就是F(r)max怎么求?

至此,問題全部解決!

#define eps 1e-8
#define zero(a) fabs(a)<eps

double v[MAXN],c[MAXN],d[MAXN];

int main()
{
    int N,K;
    scanf("%d%d",&N,&K);
    for(int i=0;i<N;i++)
        scanf("%lf%lf",&v[i],&c[i]);
    double l=0.0,r=10000000.0;
    while(!(zero(r-l)))     //二分終止條件,利用double的精度控制
    {
        double mid=(r+l)/2.0;
        for(int i=0;i<N;i++)
            d[i]=v[i]-mid*c[i];
        sort(d,d+N);
        double sum=0.0;
        for(int i=N-1;i>=N-K;i--)
            sum+=d[i];
        if(sum>=0)    l=mid;
        else    r=mid;
    }
    double ans=l;
    printf("%.2lf\n",ans);
    return 0;
}

 

二分法可以輕松水過南陽理工學院oj上的原題和ghbgh出的上大oj上的改題甚至是POJ的2976這些最基本的0-1分數規划問題。不過,用這種方法去做POJ上面的3111時卻直接TLE了orz。。。。

二分是一個非常通用的辦法,但是我們來考慮這樣的一個問題,二分的時候我們只是用到了F(r)>0這個條件,而對於使得F(r)>0的這組解所求到的R值沒有使用。因為F(r)>0,我們已經知道了R是一個更優的解,與其漫無目的的二分,為什么不將解移動到R上去呢?

 

我們再次回到函數表達圖:

這個是二分的原理表現,在圖中,我們取左界為0,右界為足夠大,畫出中間的(左+右)/2,發現此時F(r)max是大於0的,那么繼續將左界移向(左+右)/2,繼續二分,直到找到R為止。

但是我們換種思路,我們在判斷一個當前的r的時候需要去求一個F(r)max,在二分之中我們僅僅判斷了F(r)max與0的關系,這是利用率比較低的。其實我們可以將F(r)max利用起來。找到F(r)max所在的那一條直線,然后將r移動到這條直線的截距上面去(如下圖,找到當前的F(r)max所在的直線,將r移動到r4上面去,這樣做甚至只要2步即可到位)

這就是下面要介紹的方法:


二、Dinkelbach算法

它實質上是一種迭代,他是基於這樣的一個思想:他並不會去二分答案,而是先隨便給定一個答案,然后根據更優的解不斷移動答案,逼近最優解。由於他對每次判定使用的更加充分,所以它比二分會快上很多。

在這個算法中,我們可以將r初始化為任意值,不過由於所有直線都只有y軸右邊的部分,所以一般將r初始化為0。

double m[50000+10],w[50000+10];
struct node{double num;int ord;} d[50000+10];
bool cmp(node a,node b){return a.num>b.num;}
int main() { int N,K; scanf("%d%d",&N,&K); for(int i=0;i<N;i++) scanf("%lf%lf",&m[i],&w[i]); double l=0.0,ans; while(1) { ans=l; for(int i=0;i<N;i++) { d[i].num=m[i]-ans*w[i]; d[i].ord=i; } sort(d,d+N,cmp); double p=0.0,q=0.0; for(int i=0;i<K;i++) { p+=m[d[i].ord]; q+=w[d[i].ord]; } l=p/q; if(zero(ans-l)) break; } printf("%.2f\n",ans); return 0; }

 

不過需要注意的是,並不是可以放棄二分全用Dinkelbach算法。這只是最基本的0-1規划問題, Dinkelbach算法的弊端就是需要保存解。在更加復雜的問題中,有的時候二分更快,有時Dinkelbach算法更快。

二分和Dinkelbach算法寫法都非常簡單,各有長處,大家要根據題目謹慎使用。

 

 

by---Kirisame_Marisa    2014-12-26  22:31:43

 


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM