牛客網多校訓練營第五場gpa(01分數規劃)
01分數規劃
01分數規劃問題:所謂的01分數規劃問題就是指這樣的一類問題,給定兩個陣列,a[i]表示選取i的收益,b[i]表示選取i的代價。如果選取i,定義x[i]=1否則x[i]=0。每一個物品只有選或者不選兩種方案,求一個選擇方案使得R=sigma(a[i]*x[i])/sigma(b[i]*x[i])取得最值,即所有選擇物品的總收益/總代價的值最大或是最小。
01分數規劃問題主要包含一般的01分數規劃、最優比率生成樹問題、最優比率環問題等。我們將會對這三個問題進行討論。
永遠要記得,我們的目標是使R取到最值,本文主要討論取到最大值的情況。這句話我會在文中反覆的強調。
【一些分析】
數學分析中一個很重要的方法就是分析目標式,這樣我們來看目標式。
R=sigma(a[i]*x[i])/sigma(b[i]*x[i])
我們來分析一下他有什麼性質可以給我們使用。
我們先定義一個函式F(L):=sigma(a[i]*x[i])-L*sigma(b[i]*x[i]),顯然這只是對目標式的一個簡單的變形。分離引數,得到F(L):=sigma((a[i]-L*b[i])*x[i])。這時我們就會發現,如果L已知的話,a[i]-L*b[i]就是已知的,當然x[i]是未知的。記d[i]=a[i]-L*b[i],那麼F(L):=sigma(d[i]*x[i]),多麼簡潔的式子。我們就對這些東西下手了。
再次提醒一下,我們的目標是使R取到最大值。
我們來分析一下這個函式,它與目標式的關係非常的密切,L就是目標式中的R,最大化R也就是最大化L。
F的值是由兩個變數共同決定的,即方案X和引數L。對於一個確定的引數L來說,方案的不同會導致對應的F值的不同,那麼這些東西對我們有什麼用呢?
假設我們已知在存在一個方案X使得F(L)>0,這能夠證明什麼?
F(L)=sigma(a[i]*x[i])-L*sigma(b[i]*x[i])>0即sigma(a[i]*x[i])/sigma(b[i]*x[i])>L也就是說,如果一個方案使得F(L)>0說明了這組方案可以得到一個比現在的L更優的一個L,既然有一個更優的解,那麼為什麼不用呢?
顯然,d陣列是隨著L的增大而單調減的。也就是說,存在一個臨界的L使得不存在一種方案,能夠使F(L)>0. 我們猜想,這個時候的L就是我們要求的最優解。之後更大的L值則會造成無論任何一種方案,都會使F(L)<0.類似於上面的那個變形,我們知道,F(L)<0是沒有意義的,因為這時候的L是不能夠被取得的。當F(L)=0使,對應方案的R值恰好等於此時的L值。
綜上,函式F(L)有這樣的一個性質:在前一段L中可以找到一組對應的X使得F(L)>0,這就提供了一種證據,即有一個比現在的L更優的解,而在某個L值使,存在一組解使得F(L)=0,且其他的F(L)<0,這時的L無法繼續增大,即這個L就是我們期望的最優解,之後的L會使得無論哪種方案都會造成F(L)<0.而我們已經知道,F(L)<0是沒有任何意義的,因為此時的L值根本取不到。
最後一次提醒,我們的目標是R!!!
如果現在你覺得有些暈的話,那麼我要提醒你的就是,千萬不要把F值同R值混淆。F值是根據我們的變形式求的D陣列來計算的,而R值則是我們所需要的真實值,他的計算是有目標式決定的。F值只是提供了一個證據,告訴我們真正最優的R值在哪裡,他與R值本身並沒有什麼必然的聯絡。
根據這樣的一段性質,很自然的就可以想到二分L值,然後驗證是否存在一組解使得F(L)>0,有就移動下界,沒有就移動上界。
所有的01分數規劃都可以這麼做,唯一的區別就在於求解時的不同——因為每一道題的限制條件不同,並不是每一個解都是可行解的。比如在普通的陣列中,你可以選取1、2、3號元素,但在生成樹問題中,假設1、2、3號元素恰好構成了一個環,那就不能夠同時選擇了,這就是需要具體問題,具體分析的部分。
二分是一個非常通用的辦法,但是我們來考慮這樣的一個問題,二分的時候我們只是用到了F(L)>0這個條件,而對於使得F(L)>0的這組解所求到的R值沒有使用。因為F(L)>0,我們已經知道了R是一個更優的解,與其漫無目的的二分,為什麼不將解移動到R上去呢?求01分數規劃的另一個方法就是
,他就是基於這樣的一個思想,他並不會去二分答案,而是先隨便給定一個答案,然後根據更優的解不斷移動答案,逼近最優解。由於他對每次判定使用的更加充分,所以它比二分會快上很多。但是,他的弊端就是需要儲存這個解,而我們知道,有時候驗證一個解和求得一個解的複雜度是不同的。二分和Dinkelbach演算法寫法都非常簡單,各有長處,大家要根據題目謹慎使用。
本題程式碼:
#include <bits/stdc++.h>
#define maxn 100005
using namespace std;
typedef long long ll;
int s[maxn],c[maxn];
double y[maxn];
int n,k;
bool cmp(double a,double b){
return a>b;
}
bool check(double x){
for(int i=0;i<n;i++){
y[i]=c[i]*s[i]-s[i]*x;
}
//sort(y,y+n,cmp);
nth_element(y,y+k,y+n,cmp);
double sum=0;
for(int i=0;i<k;i++){
sum+=y[i];
}
return sum>=0;
}
int main()
{
scanf("%d%d",&n,&k);
k=n-k;
for(int i=0;i<n;i++){
scanf("%d",&s[i]);
}
for(int i=0;i<n;i++){
scanf("%d",&c[i]);
}
double l=0,r=1e10;
for(int i=0;i<60;i++){//浮點數的獨特的二分方法
double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%.7f\n",r);
}