分治 —— 01 分数规划

【概述】

分数规划的一般形式为:min(\lambda )/max(\lambda )=f(x)=\frac{\sum a(x)}{\sum b(x)}

特别的,当 min(\lambda )/max(\lambda )=f(x)=\frac{a*x}{b*x}(x\in \{0,1\}^n) 时,称为 01 分数规划

简单来说,就是有一些二元组 (a[i],b[i]),现在从中选择某些二元组,使得 \frac{\sum a[i]}{\sum b[i]} 最大或最小

这一类题通用的解法是利用二分法来解决:

假设 x 为最优解,对应的最优函数值为 λ,那么有:\lambda *\sum b(x)=\sum a(x)

即:{\sum a(x)}-x*{\sum b(x)}=0

于是可以构造函数 G(\lambda )=min/max(\sum a(x)-\lambda b(x))

当 G(\lambda )=0 时,此时 λ 即为最优解

也就是说,当某一个值 λ 满足上述式子的时候,就是要求的值,因此可以直接二分答案:当上述式子 >0,说明答案小了,更改左区间;当上述式子 <0,说明答案大了,更改右区间

double left=0,right=1;
while(left-right<=EPS) {
    double mid=(left+right)/2;
    double G=INF;
    for(int i=1;i<=n;i++) 
        G=min(G, a[i]-mid*b[i]);
    if(judge(G))
        left=mid;
    else
        right=mid;
}

【Dinkelbach 算法】

对于 01 规划问题,除了利用基本的二分答案来解决外,还有一个常用的算法:Dinkelbach 算法

Dinkelbach 算法是一种的迭代算法,其核心思想是:对于一个值 λ ,我们找到其最优解 x,然后再用解 x 来代替 λ ,即令 λ=f(x),然后继续迭代,直到 λ 的值不再变动,此时即得出最优解。

double init=0.5;//初始值可随意设置
while(fabs(init-res)>EPS) {
    res=left;//记录比率
    for(int i=1;i<=n;i++)//计算函数G 
        G=min/max(G,a[i]-init*b[i]);

    double p=0,q=0;
    for(int i=1;i<=m;i++){//计算新比率
        p+=a[i];
        q+=b[i];
    }
    init=p/q;//更新比率
}

【常见模型】

01 规划问题,除了上述的基本模型外,还有以下三种常见的模型

1.最优比率生成树

1)问题:对于给定的带权无向图 G,对于图中的每条边 edge[i],都有 value[i] 与 cost[i],现在要求一棵生成树 T,使得 \frac{\sum value[i]}{\sum cost[i]}最大化或最小化,这个生成树 T,即为最优比率生成树

2)解决:如果一条边 edge[i]∈T,那么 x[i]=1,否则 x[i]=0,因此可以直接套用 01 规划分数模型

二分法:二分答案 mid,对边重赋值 weight[i]=value[i]-mid*cost[i],由于是生成树,因此边的数量固定为 n-1 条,因此如果要最大化,只要选取前 n-1 大的 weight[i],也即求最大生成树,如果要最小化,就求最小生成树,最后按最大生成树的权值正负性进行二分

Dinkelbach 算法:设置初始值 init,对边同样重赋值 weight[i]=value[i]-init*cost[i],对于最大化来说,求最大生成树,找到其边集,对其边集找横截距当作下一次的答案,进行迭代

2.最优比率环

1)问题:给定一个存在环的有向图 G,每个点都有一个权值 value[i],每条边也有一条权值 cost[i],现在要求一个环 C,使得环的 \frac{\sum value[i]}{\sum cost[i]}最大化或最小化(在一个环中,点数与边数是相同的),这个环,即为最优比率环

2)解决:

假设答案为 x,那么对于任意一个环

当要最大化时,有:\frac{\sum value[i]}{\sum cost[i]}\leqslant x,化简得:\sum value[i]\leqslant x*\sum cost[i]

即:x*\sum cost[i]-\sum value[i]\geqslant 0

同理,当要最小化时,有:\sum value[i]-x*\sum cost[i]\geqslant 0

因此可以直接套用 01 规划分数模型

二分法:设当前答案为 mid,当 mid<x 时,至少存在一个环,使得 mid*\sum cost[i]-\sum value[i]<0,即存在负权回路;当 mid>=x 时,不存在负环,因此可在利用 SPFA 求负环的过程中对边进行重赋值,从而进行二分,即如果存在负环,即增大下界,若一直不可行,则无解

Dinkelbach 算法:由于使用 Dinkelbach 算法需要在不断迭代的过程中记录负环,实现较为复杂,因此一般在该模型中不采用该方法

3.最大密度子图

`1)问题:给出一个 n 个点 m 条边的无向图,现在要选择一个子图,使得这个子图中边数与点数的比例最大化,这个子图,即为最大密度子图

2)解决:

设 g 为最大比率,若要找一个最大密度子图,那么可以参考 01分数规划的基本模型来构造一个函数 h(g)=|E'|-g*|V'|,当 h(g)=0 时,g 即为最优值

关于两种解法的详细证明参见 胡伯涛的《最小割模型在信息学竞赛中的应用》点击这里

二分+最大权闭合图:

可以发现,边与点具有依赖关系,即边存在的必要条件是点的存在,那么我们可以将边看作点,根据 g 的值来构建一个新图,即:将原图中所有代表边的点向他的两个端点连接一条有向边

那么可以直观的看出,如果选择一条边,那么这条边的两个端点必然也被选择,也就是一个闭合子图的模型

因此,在二分 g 的过程中,不断的建图来判断 g 的正负从而调整边界即可

void makeMap(double g) {
    memset(head,-1,sizeof(head));  
    tot=0;

    for(int i=1;i<=n;i++)//原图中的点到汇点 
        addedge(i,T,g); 
    for(int i=0;i<m;i++) {  
        //源点到原图中的每条边
        addedge(S,n+i+1,1.0);
        //原图中的每条边到之间建边
        addedge(n+i+1,pastEdge[i].first,INF);  
        addedge(n+i+1,pastEdge[i].second,INF);  
    }  
}

二分+最小割:

设 g 为最大比率,在建图时从源点到各点连接一条容量为 m 的单向边,从各点到汇点连接一条容量为 m+2*g-degree[i] 的单向边,其中 degree[i] 代表第 i 个点的度数,这样以后对这个图求最小割,那么 h(g)=(n*m-maxFlow)/2

因此在二分时,通过 g 值求出的最小割来判断正负,于是根据这个结果的正负就可以修改左右边界,直到达到一个最优值

void makeMap(double g) {
    memset(head,-1,sizeof(head));  
    tot=0;

    for (int i = 1; i <= n; i++) {//原图中的点
        addedge(S, i, m * 1.0);
        addedge(i, T, m + 2 * g - degree[i]);
    }
    for (int i = 0; i < m; i++) {//原图中的边
        addedge(pastEdge[i].first, pastEdge[i].second, 1.0);
        addedge(pastEdge[i].second, pastEdge[i].first, 1.0);
    } 
}

【例题】

  • Dropping tests(POJ-2976)(标准模型+二分解法)点击这里
  • Dropping tests(POJ-2976)(标准模型+Dinkelbach 算法)点击这里
  • Desert King(POJ-2728)(最优比率生成树+二分解法)点击这里
  • Desert King(POJ-2728)(最优比率生成树+Dinkelbach 算法)点击这里
  • Sightseeing Cows(POJ-3621)(最优比率环+二分解法)点击这里
  • Hard Life(POJ-3155)(最大密度子图+最大权闭合图)点击这里
  • Hard Life(POJ-3155)(最大密度子图+最小割)点击这里
发布了1871 篇原创文章 · 获赞 702 · 访问量 194万+

猜你喜欢

转载自blog.csdn.net/u011815404/article/details/102386410