【学习笔记】最大密度子图

122 篇文章 0 订阅
27 篇文章 0 订阅

概述

这是一个简单的图论问题:求一个无向连通图 G G G 的子图,使得 ∣ E ′ ∣ ∣ V ′ ∣ |E'|\over|V'| VE 最大。后文中称呼 ∣ E ′ ∣ ∣ V ′ ∣ |E'|\over|V'| VE 为该子图的 密度

注:是否要求该子图为连通图并不重要——如果子图不连通,那么两个连通分量中可以取 ∣ E ∣ ∣ V ∣ |E|\over|V| VE 较大的一个,所以只会保留一个连通分量。

第一步转化

考虑 0 / 1 0/1 0/1 分数规划。二分答案后,选择一条边则增加 1 1 1 的权值,选择一个点则减少 w w w 的权值。

只需要判断最大权值是否为正。

方案一

首先有一个简单的方案:选择一条边则必须选择其端点。这可以转化为最大权闭合子图。

但是,我们把边和点都视为物品,所以新的图中有 O ( n + m ) \mathcal O(n+m) O(n+m) 个点,在稠密图中,这是非常大的开销。

方案二

注意边和点并不是那么独立的物品。比如说,很明显地,选择了一个点集之后,一定会选择导出子图。

那么导出子图怎么算边数呢?考虑用度数。如果记 c ( S ) c(S) c(S) 为,有多少条边满足只有一个端点在 S S S 中,则
∣ E ′ ∣ = 1 2 [ ∑ x ∈ V ′ deg ⁡ ( x ) − c ( V ′ ) ] |E'|=\frac{1}{2}\left[\sum_{x\in V'}\deg(x)-c(V')\right] E=21[xVdeg(x)c(V)]
此时我们惊讶地发现: c ( V ′ ) c(V') c(V) 正好是一个割。它不就是使得 V ′ V' V 与其补集不连通的删边数量吗?

于是考虑回到网络流,但是此时只有点是物品。为了与最小割相符,考虑最小化 w ∣ V ′ ∣ − ∣ E ′ ∣ w|V'|-|E'| wVE

此时,选中一个点 x x x 可以提供权值 w − 1 2 deg ⁡ ( x ) w-\frac{1}{2}\deg(x) w21deg(x),而两个相邻的点如果只有一个被选中,就提供 − 1 2 -\frac{1}{2} 21 的贡献。由于分数太麻烦,我们又只判定正负,所以去掉 1 2 \frac{1}{2} 21,两个权值都 × 2 \times 2 ×2

不妨设 S S S 部中的点表示被选中,建出网络流图,即 x x x T T T 连边,容量为 2 w − deg ⁡ ( x ) 2w-\deg(x) 2wdeg(x),原图中有连边的两个点连双向边,容量为 1 1 1 。此时你一看:根本没有流量

问题出在哪里?在于 2 w − deg ⁡ ( x ) 2w-\deg(x) 2wdeg(x) 非正,不能作为流量。于是我们可以让 x x x 被选和不被选择的权值都增加 r r r,也就是 S S S x x x 连边,容量为 r r r,但是 x x x T T T 的连边容量变为 2 w − deg ⁡ ( x ) + r 2w-\deg(x)+r 2wdeg(x)+r 。显然这会让我们希望的最大流(也就是有负容量的最小割)增加 n r nr nr,最后减去即可。取 r = m r=m r=m 就可以让边权变为正数。

一个细节

但是上面的做法是正确的吗?我们好像忽略了一点:网络流允许 S = { s } S=\{s\} S={s},也就是说,选出了 V ′ = ∅ V'=\varnothing V=,这时候权值一定为 0 0 0 。太糟了。

但是利用我们 0 / 1 0/1 0/1 分数规划的知识:当 w w w 越大时,求出的最小割就越大(并且是严格递增的),唯一的零点就是答案。而 V ′ = ∅ V'=\varnothing V= 则相当于将函数值与 0 0 0 取较小值。那么显然零点就变成了原本的零点与其右侧的所有点。问题转化为,求出最靠左的零点。

那么,如果当前函数值(检测值)是零,说明答案应当不超过该值;如果函数值不为零,则答案应该大于该值。

另一个细节

那么二分到何时截止呢?其实很容易发现,任意两个子图的密度差不小于 1 n 2 \frac{1}{n^2} n21,除非密度相等。证明是极其简单的,分数作差时,差值的分母最大是两个分母的乘积即 n 2 n^2 n2,分子至少为 1 1 1 嘛。

所以答案的范围大约在 O ( 1 n 2 ) \mathcal O(\frac{1}{n^2}) O(n21) 级别时,就可以停止了。

代码实现

略去了网络流的部分,也略去了数据读入部分。使用链式前向星存图,边从 0 0 0 开始存储,故 h e a d = − 1 head=-1 head=1 为终止。

int deg[MAXN], n, m;
bool check(double w){
	cntEdge = m<<1; // restore
	head[n+1] = head[n+2] = -1;
	for(int i=1; i<=n; ++i)
		while(head[i] >= cntEdge)
			head[i] = e[head[i]].nxt;
	for(int i=1; i<=n; ++i){
		for(int j=head[i]; ~j; j=e[j].nxt)
			e[j].val = 1; // restore
		addEdge(n+1,i,m); // source
		addEdge(i,n+2,2*w-deg[i]+m);
	}
	return dinic(n+1,n+2) >= n*m;
}
int main(){
	const double GAP = 0.05/n/n;
	double ansl = 0, ansr = 50;
	while(ansl+GAP < ansr)
		if(check((ansl+ansr)/2) == false)
			ansl = (ansl+ansr)/2;
		else ansr = (ansl+ansr)/2;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值