分治 —— 01 分数规划
【概述】
分数规划的一般形式为:
特别的,当 时,称为 01 分数规划
简单来说,就是有一些二元组 (a[i],b[i]),现在从中选择某些二元组,使得 最大或最小
这一类题通用的解法是利用二分法来解决:
假设 x 为最优解,对应的最优函数值为 λ,那么有:
即:
于是可以构造函数
当 时,此时 λ 即为最优解
也就是说,当某一个值 λ 满足上述式子的时候,就是要求的值,因此可以直接二分答案:当上述式子 >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,使得 最大化或最小化,这个生成树 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,使得环的 最大化或最小化(在一个环中,点数与边数是相同的),这个环,即为最优比率环
2)解决:
假设答案为 x,那么对于任意一个环
当要最大化时,有:,化简得:
即:
同理,当要最小化时,有:
因此可以直接套用 01 规划分数模型
二分法:设当前答案为 mid,当 mid<x 时,至少存在一个环,使得 ,即存在负权回路;当 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);
}
}