0/1 分数规划
0/1 分数规划
0/1 分数规划模型是指,给定整数 \(a_1,a_2,\dots,a_n\) 和 \(b_1,b_2,\dots,b_n\),求一组解 \(x_i\in[0,1]\),使下式最大/最小化:
\[\frac{\sum_{i=1}^na_ix_i}{\sum_{i=1}^nb_ix_i}
\]
换句话说,选一定的 \(a_i\) 和 \(b_i\) 组成一个集合 \(S\),求
\[\frac{\sum a_i\in S}{\sum b_i\in S}
\]
的最值。
这种问题的一般解决方法是设答案为 \(\mathit{ans}\),即
\[\mathit{ans}=\frac{\sum a_i}{\sum b_i}~,
\]
然后将式子变形,得
\[\mathit{ans}\times\sum b_i+\sum a_i=0
\]
即
\[\sum(\mathit{ans}\times b_i+a_i)=0
\]
答案具有可二分性。所以我们解决这类问题的方法一般就是二分答案,初始 \(l=0,r=\infty\),每次判定 \(\sum(\mathit{mid}\times b_i+a_i)\) 的正负,直到找到在误差范围内最接近的答案。复杂度是 \(O(\log V)\) 的。
还有一种更高级的方法叫做 Dinkelbach 迭代法,没用。
最优比率生成树
一般来说 0/1 分数规划不会直接考,而是套在一些别的东西中,比如最优比率生成树。题意一般是给定一个图,每条边有两个边权 \(a_i,b_i\),求这张图的生成树的 \(\dfrac{\sum a_i}{\sum b_i}\)。
依然二分,每次跑一遍 Kruskal 求出上面那个式子,然后二分答案判定即可。
[ARC026D] 道を直すお仕事
板子题,但注意到这题并不要求一定是棵生成树,所以边权为负的边一定是会选的,把所有边权为负的边选完之后再判断边权为正的边选不选。
using ll=long long;
constexpr int MAXN=10005,MAXM=10005;
constexpr double eps=1e-6;
int n,m;
struct Edge{
ll u,v,c,t;
}e[MAXM];
int f[MAXN];
int fnd(int x){
return f[x]==x?x:f[x]=fnd(f[x]);
}
double kruskal(double x){
iota(f+1,f+n+1,1);
int s=0;
for(int i=1;i<=m;++i) if(x*e[i].t-e[i].c>=-eps) ++s;
sort(e+1,e+m+1,[&](const Edge&a,const Edge&b){
return x*a.t-a.c>x*b.t-b.c;
});
double res=0;
for(int i=1;i<=s;++i){
f[fnd(e[i].u)]=fnd(e[i].v);
res+=x*e[i].t-e[i].c;
}
for(int i=s+1;i<=m;++i){
int fx=fnd(e[i].u),fy=fnd(e[i].v);
if(fx==fy) continue;
f[fy]=fx;
res+=x*e[i].t-e[i].c;
}
return res;
}
int main(){
n=read(),m=read();
for(int i=1;i<=m;++i) e[i]={read()+1,read()+1,read(),read()};
double l=0,r=1e18;
while(r-l>eps){
double mid=(l+r)/2;
if(kruskal(mid)<-eps) l=mid;
else r=mid;
}
printf("%.4f\n",l);
return 0;
}
P4951 [USACO01OPEN] Earthquake
这道题求的是 \(\dfrac{F-\sum a_i}{\sum b_i}\)。同理,将原式变换为 \(F=\sum(\mathit{ans}\times b_i+a_i)\),每次二分答案判定和 \(F\) 的大小关系即可。
即得易见平凡,仿照上例显然。留作习题答案略,读者自证不难。
反之亦然同理,推论自然成立。略去过程 $\rm QED$,由上可知证毕。

浙公网安备 33010602011771号