浅谈01分数规划
我扔:https://www.luogu.org/problemnew/show/P1642
这题是一道很好的01分数规划的入门题
为啥叫做01分数规划呢?
首先和01有什么关系?
这类问题一般是给定一堆
a
[
i
]
a[i]
a[i] 和
b
[
i
]
b[i]
b[i] ,然后如果选了
a
[
i
]
a[i]
a[i]就必须选
b
[
i
]
b[i]
b[i] 感性理解一下, 就是比如说选第i个物品会有
a
[
i
]
a[i]
a[i]的收益,但是有
b
[
i
]
b[i]
b[i]的花费, 求
选
出
来
的
a
[
i
]
选
出
来
的
b
[
i
]
\frac{选出来的a[i]}{选出来的b[i]}
选出来的b[i]选出来的a[i] 最大。
于是就可以多定义一个
x
[
1...
n
]
x[1...n]
x[1...n] 数组,
x
[
i
]
x[i]
x[i] 为0或1表示选或不选,则对于每种
x
[
1...
n
]
x[1...n]
x[1...n]的答案可以表示为
∑ a [ i ] × x [ i ] b [ i ] × x [ i ] \sum {\frac{a[i] \times x[i]}{b[i] \times x[i]}} ∑b[i]×x[i]a[i]×x[i]
那么这类问题称为01分数规划问题
那怎么做呢?
假设答案为
A
N
S
=
∑
a
[
i
]
×
x
[
i
]
b
[
i
]
×
x
[
i
]
ANS = \sum {\frac{a[i] \times x[i]}{b[i] \times x[i]}}
ANS=∑b[i]×x[i]a[i]×x[i]
则
A
N
S
×
∑
b
[
i
]
×
x
[
i
]
=
∑
a
[
i
]
×
x
[
i
]
ANS \times \sum {b[i] \times x[i]} = \sum {a[i] \times x[i]}
ANS×∑b[i]×x[i]=∑a[i]×x[i]
∑
a
[
i
]
×
x
[
i
]
−
A
N
S
×
∑
b
[
i
]
×
x
[
i
]
=
0
\sum {a[i] \times x[i]} - ANS \times \sum {b[i] \times x[i]} = 0
∑a[i]×x[i]−ANS×∑b[i]×x[i]=0
x
[
i
]
×
∑
(
a
[
i
]
−
A
N
S
×
b
[
i
]
)
=
0
x[i] \times \sum ({a[i] - ANS \times b[i]}) = 0
x[i]×∑(a[i]−ANS×b[i])=0
令
d
[
i
]
=
a
[
i
]
−
A
N
S
×
b
[
i
]
d[i] = a[i] - ANS \times b[i]
d[i]=a[i]−ANS×b[i]
通常是规定了选几个,所以只需要按照
d
[
i
]
d[i]
d[i]的大小来选就可以了,
那ANS呢?
直接二分啊!!!
此题代码:
#include<bits/stdc++.h>
#define N 1000005
#define INF 23333333
using namespace std;
const double eps = 0.000001;
struct edge{
int v, nxt;
}e[N];
int p[N], eid;
void init(){
memset(p, -1, sizeof p);
eid = 0;
}
void insert(int u, int v){
e[eid].v = v;
e[eid].nxt = p[u];
p[u] = eid ++;
}
double f[105][105], d[105];
int vis[105], size[105], n, m, a[105], b[105];
void dfs(int x){//简单的树dp, f[i][j]表示第 i 个点, 选 j 个的方案数, 其中 i 必须选
vis[x] = 1; size[x] = 1;
f[x][1] = d[x];f[x][0] = 0;
for(int i = p[x]; i + 1; i = e[i].nxt){
int v = e[i].v;
if(vis[v]) continue;
dfs(v); size[x] += size[v];
for(int j = min(m, size[x]); j >= 1; j --){
for(int k = 0; k < j; k ++){
f[x][j] = max(f[x][j], f[x][j - k] + f[v][k]);
}
}
}
}
int check(double x){
for(int i = 1; i <= n; i ++) d[i] = a[i] * 1.0 - b[i] * 1.0 * x, vis[i] = 0, size[i] = 0; //求 d[i]
for(int i = 1; i <= n; i ++)
for(int j = 0; j <= m; j ++) f[i][j] = -INF;
dfs(1);
for(int i = 1; i <= n; i ++){
if(f[i][m] > -eps) return 1; //如果有满足要求的就退出
}
return 0;
}
int main(){
init();
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
for(int i = 1; i <= n; i ++) scanf("%d", &b[i]);
m = n - m;
for(int i = 1; i < n; i ++){
int u, v;
scanf("%d%d", &u, &v);
insert(u, v);
insert(v, u);
}
double l = 0, r = 100000;
while(l + eps < r){ //二分答案
double mid = (l + r) * 1.0 / 2.0;
if(check(mid)) l = mid;
else r = mid;
}
printf("%.1f", l);
return 0;
}
emmmmmm……
再推荐几道入门题(前两题转换为求01分数规划判断负环即可):
https://www.luogu.org/problemnew/show/P1768
https://www.luogu.org/problemnew/show/P2868
https://www.luogu.org/problemnew/show/P1730 (暴力SPFA预处理答案)