淺談01分數規划
所謂01分數規划,看到這個名字,可能會想到01背包,其實長得差不多。
這個算法就是要求“性價比”最高的解。sum(v)/sum(w)最高的解。
定義
我們給定兩個數組,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分數規划
- 最優比率生成樹
- 最優比率環
關於01分數規划的關鍵
F(L)=sigma(a[i]x[i])-Lsigma(b[i]x[i])
F(L)=sigma(a[i]x[i]-Lb[i]a[i])
F(L)=sigma((a[i]-Lb[i])x[i])
我們把a[i]-L*b[i]定義為d[i],這樣我們的算式就變成了以下算式。
F(L)=sigma(d[i]*x[i])
這樣我們就把這個繁瑣的算式變成了一個非常優美的算式。
而01分數規划就是要枚舉L在求最大值或最小值的F(L)。
在實現程序的過程中,我們使用一個非常熟悉的老朋友,要求最大或最小,所以??我們就要用二分
關於為什么01分數規划不能用貪心?
(個人看法)
如果硬要貪心,那么就只有可能是算出每一個物品的性價比,在排序求出最大或者最小的性價比,在累加算出答案。
一、01分數規划算法
先設置價值數組a[i]和代價數組b[i],我們的答案為R。
簡單來說 $$ R=\frac{\sum{valuei}}{\sum{weighti}} $$
我們可以發現,R的大小與上下的總值有關。
二、貪心算法
我們反觀一下貪心算法,先算出每個物品的性價比
那么貪心得到的答案就是
比較
我們可以很容易發現,01分數規划和貪心的得到的答案有明顯的區別,一個是總價值/總代價,而貪心中只是單價值/單代價的累加,而不只是比值的大小,而取決於分母和分子的大小,所以這兩個東西不相等
例題
一、POJ-2976Dropping tests
Description
In a certain course, you take n tests. If you get ai out of bi questions correct on test i, your cumulative average is defined to be
Given your test scores and a positive integer k, determine how high you can make your cumulative average if you are allowed to drop any k of your test scores.
Suppose you take 3 tests with scores of 5/5, 0/1, and 2/6. Without dropping any tests, your cumulative average is . However, if you drop the third test, your cumulative average becomes .
Input
The input test file will contain multiple test cases, each containing exactly three lines. The first line contains two integers, 1 ≤ n ≤ 1000 and 0 ≤ k < n. The second line contains n integers indicating ai for all i. The third line contains n positive integers indicating bi for all i. It is guaranteed that 0 ≤ ai ≤ bi ≤ 1, 000, 000, 000. The end-of-file is marked by a test case with n = k = 0 and should not be processed.
Output
For each test case, write a single line with the highest cumulative average possible after dropping k of the given test scores. The average should be rounded to the nearest integer.
Sample Input
3 1
5 0 2
5 1 6
4 2
1 2 7 9
5 6 7 9
0 0
Sample Output
83
100
Analysis
01分數規划的入門題,我們就認真講解一下。
首先我們要理解題目的意思,大意是:給你一個價值a[i]和代價b[i],然后我們選舉n-k個物品,使得總價值/總代價。
理解了題目后,我們就是要用到了今天學習的01分數規划。
我們把這個算式進行一個變形:
上文已經分析過如何進行移項便是以下的算式:
根據這個算式,因為a[i]和b[i]是已知的,所以我們就把$ ai-lbi\(這個算式定義成\) di \( 所以原來的又長又臭的算式就可以成為\)F(l)=\sum dixi \( 這樣我們的算式中只有\)l\(是未知的,我們只需要用二分來枚舉\)l\(在算出\)F(l)$就可以了。
Code
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <cctype>
#include <cmath>
#include <time.h>
#include <map>
#include <set>
#include <vector>
using namespace std;
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
const double eps=1e-7;
int n,k;
double a[1010],b[1010],d[1010];
inline int read()
{
int X=0,w=0; char ch=0;
while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
return w?-X:X;
}
int main()
{
while (1)
{
n=read(),k=read();
if (n==0 && k==0) break;
for (int i=1;i<=n;i++) scanf("%lf",&a[i]);
for (int i=1;i<=n;i++) scanf("%lf",&b[i]);
double l=0.0,r=1.0,mid;
while (r-l>eps)
{
mid=(l+r)/2;
for (int i=1;i<=n;i++) d[i]=a[i]-mid*b[i];
sort(d+1,d+1+n);
double sum=0.0;
for (int i=k+1;i<=n;i++) sum+=d[i];
if (sum>0) l=mid;
else r=mid;
}
printf("%.0f\n",mid*100);
}
return 0;
}
二、51nod背包問題 V3
Description
N個物品的體積為W1,W2......Wn(Wi為整數),與之相對應的價值為P1,P2......Pn(Pi為整數),從中選出K件物品(K <= N),使得單位體積的價值最大。
Input
第1行:包括2個數N, K(1 <= K <= N <= 50000)
第2 - N + 1行:每行2個數Wi, Pi(1 <= Wi, Pi <= 50000)
Output
輸出單位體積的價值(用約分后的分數表示)。
Sample Input
3 2
2 2
5 3
2 1
Sample Output
3/4
Analysis
入門題,我們算出答案后,一個gcd就好了。
Code
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <cctype>
#include <cmath>
#include <time.h>
#include <map>
#include <set>
#include <vector>
using namespace std;
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
const int maxn = 50005;
const double eps = 1e-9;
ll gcd(ll x,ll y){return y?gcd(y,x%y):x;}
struct node
{
int w,p;
double val;
bool operator < (const node& T) const{
return val>T.val;
}
}b[maxn];
double mid;
ll anss,ansx,tmps,tmpx;
int n,k;
bool check()
{
int tot=0;
for(int i=0;i<n;i++)b[i].val = 1.0*b[i].p-b[i].w*mid;
sort(b,b+n);
double sum=0;
tmps=0,tmpx=0;
for(int i=0;i<k;i++)sum+=b[i].val,tmps+=b[i].p,tmpx+=b[i].w;
if(sum-0>=eps)return true;
return false;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++) scanf("%d%d",&b[i].w,&b[i].p);
double l=0,r=500000;
for(int i=0;i<100;i++)
{
mid=(l+r)/2;
if(check())l=mid,anss=tmps,ansx=tmpx;
else r=mid;
}
ll tmp=gcd(anss,ansx);
anss/=tmp,ansx/=tmp;
printf("%lld/%lld\n",anss,ansx);
return 0;
}
三、POJ - 2728Desert King
Description
David the Great has just become the king of a desert country. To win the respect of his people, he decided to build channels all over his country to bring water to every village. Villages which are connected to his capital village will be watered. As the dominate ruler and the symbol of wisdom in the country, he needs to build the channels in a most elegant way.
After days of study, he finally figured his plan out. He wanted the average cost of each mile of the channels to be minimized. In other words, the ratio of the overall cost of the channels to the total length must be minimized. He just needs to build the necessary channels to bring water to all the villages, which means there will be only one way to connect each village to the capital.
His engineers surveyed the country and recorded the position and altitude of each village. All the channels must go straight between two villages and be built horizontally. Since every two villages are at different altitudes, they concluded that each channel between two villages needed a vertical water lifter, which can lift water up or let water flow down. The length of the channel is the horizontal distance between the two villages. The cost of the channel is the height of the lifter. You should notice that each village is at a different altitude, and different channels can't share a lifter. Channels can intersect safely and no three villages are on the same line.
As King David's prime scientist and programmer, you are asked to find out the best solution to build the channels.
Input
There are several test cases. Each test case starts with a line containing a number N (2 <= N <= 1000), which is the number of villages. Each of the following N lines contains three integers, x, y and z (0 <= x, y < 10000, 0 <= z < 10000000). (x, y) is the position of the village and z is the altitude. The first village is the capital. A test case with N = 0 ends the input, and should not be processed.
Output
For each test case, output one line containing a decimal number, which is the minimum ratio of overall cost of the channels to the total length. This number should be rounded three digits after the decimal point.
Sample Input
4
0 0 0
0 1 1
1 1 2
1 0 3
0
Sample Output
1.000
Analysis
大意:給定一張圖,每條邊有一個收益值和一個花費值, 求一個生成樹,要求花費/收益最小,輸出這個值
分析:現在的限制就有點復雜了,要求解必須是一棵生成 樹。而且這道題目要求的花費/收益最小,當然你求收益/ 花費最大然后反過來也是可以的,注意處理花費為0的情 況。如果求最小的,處理方法是也類似的,先求個D,然 后做一次最小生成樹,顯然得到的就是函數值。
Code

四、POJ-3621Sightseeing Cows
Description
Farmer John has decided to reward his cows for their hard work by taking them on a tour of the big city! The cows must decide how best to spend their free time.
Fortunately, they have a detailed city map showing the L (2 ≤ L ≤ 1000) major landmarks (conveniently numbered 1.. L) and the P (2 ≤ P ≤ 5000) unidirectional cow paths that join them. Farmer John will drive the cows to a starting landmark of their choice, from which they will walk along the cow paths to a series of other landmarks, ending back at their starting landmark where Farmer John will pick them up and take them back to the farm. Because space in the city is at a premium, the cow paths are very narrow and so travel along each cow path is only allowed in one fixed direction.
While the cows may spend as much time as they like in the city, they do tend to get bored easily. Visiting each new landmark is fun, but walking between them takes time. The cows know the exact fun values Fi (1 ≤ Fi ≤ 1000) for each landmark i.
The cows also know about the cowpaths. Cowpath i connects landmark L1i to L2i (in the direction L1i -> L2i ) and requires time Ti (1 ≤ Ti ≤ 1000) to traverse.
In order to have the best possible day off, the cows want to maximize the average fun value per unit time of their trip. Of course, the landmarks are only fun the first time they are visited; the cows may pass through the landmark more than once, but they do not perceive its fun value again. Furthermore, Farmer John is making the cows visit at least two landmarks, so that they get some exercise during their day off.
Help the cows find the maximum fun value per unit time that they can achieve.
Input
- Line 1: Two space-separated integers: L and P
- Lines 2..L+1: Line i+1 contains a single one integer: Fi
- Lines L+2..L+P+1: Line L+i+1 describes cow path i with three space-separated integers: L1i , L2i , and Ti
Output
- Line 1: A single number given to two decimal places (do not perform explicit rounding), the maximum possible average fun per unit time, or 0 if the cows cannot plan any trip at all in accordance with the above rules.
Sample Input
5 7
30
10
10
5
10
1 2 3
2 3 2
3 4 5
3 5 2
4 5 5
5 1 3
5 2 2
Sample Output
6.00
Analysis
大意:給定一張圖,邊上有花費,點上有收益,點可以多 次經過,但是收益不疊加,邊也可以多次經過,但是費用 疊加。求一個環使得收益和/花費和最大,輸出這個比值。
分析:比上面更加的惡心了。先不說環的問題,就是花費 和收益不在一處也令人蛋疼。這時候需要用到幾個轉化和 結論。
首先的一個結論就是,不會存在環套環的問題,即最優的方 案一定是一個單獨的環,而不是大環套着小環的形式。這個的 證明其實非常的簡單,大家可以自己想一下(提示,將大環上 的收益和記為x1,花費為y1,小環上的為x2,y2。重疊部分的花 費為S。表示出來分類討論即可)。有了這個結論,我們就可以 將花費和收益都轉移到邊上來了,因為答案最終一定是一個環, 所以我們將每一條邊的收益規定為其終點的收益,這樣一個環 上所有的花費和收益都能夠被正確的統計。
解決了蛋疼的問題之后,就是01分數規划的部分了,我們只 需要計算出D數組后找找有沒有正權環即可,不過這樣不太好, 不是我們熟悉的問題,將D數組全部取反之后,問題轉換為查找 有沒有負權環,用spfa或是bellman_ford都可以。
Code
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#define N 100010
using namespace std;
const double eps=1e-5;
struct edge
{
int v,nxt,w;
double c;
} e[N<<1];
int head[N],f[N];
bool vis[N];
double dis[N];
int n,m,mct,u,v,w;
inline int read()
{
int x=0,f=1;char c=getchar();
while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
void add(int u,int v,int w)
{
e[++mct].v=v;e[mct].nxt=head[u];e[mct].w=w;head[u]=mct;
}
bool spfa(int u)
{
vis[u]=1;
for(int i=head[u]; i; i=e[i].nxt)
{
int v=e[i].v;
if(dis[v]>dis[u]+e[i].c)
{
dis[v]=dis[u]+e[i].c;
if(vis[v] || spfa(v))
{
vis[v]=0;
return 1;
}
}
}vis[u]=0;return 0;
}
void judge(double r)
{
for(int i=1; i<=mct; i++)
e[i].c=(double)e[i].w*r-f[e[i].v];
return;
}
bool check()
{
for(int i=1; i<=n; i++)
if(spfa(i))return 1;
return 0;
}
int main()
{
n=read();m=read();
for(int i=1; i<=n; i++) f[i]=read();
for(int i=1; i<=m; i++)
{
u=read();v=read();w=read();
add(u,v,w);
}
double l=0,r=100000,ans;
while(r-l>eps)
{
double mid=(l+r)/2;
judge(mid);
if(check())
{
ans=mid;l=mid;
}
else r=mid;
}
printf("%.2f\n",ans);
return 0;
}
