最小割之最大密度子图

最小割之最大密度子图

n n n 个点 m m m 条边的无向图 G = { V , E } G=\left\{V,E\right\} G={V,E},求最大密度子图 G ′ = { V ′ , E ′ } G'=\left\{V',E'\right\} G={V,E} ,即子图有 M a x i m i z e ∣ E ′ ∣ ∣ V ′ ∣ Maximize \frac{|E'|}{|V'|} MaximizeVE

分析

答案 M a x i m i z e ∣ E ′ ∣ ∣ V ′ ∣ = ∑ e ∈ E x e ∑ v ∈ V x v \Huge{答案 Maximize \frac{|E'|}{|V'|} =\frac{\sum_{e\in E}x_e}{\sum_{v\in V}x_v}} 答案MaximizeVE=vVxveExe

其中 x e , x v ∈ 0 , 1 x_e, x_v \in {0,1} xe,xv0,1,如果是 1 1 1 表示在子图中, 0 0 0 表示不在子图中

并且如果边 ( u , v ) (u,v) (u,v) 在子图中,边 ( u , v ) (u,v) (u,v) 的两个端点 u , v u,v u,v 也必然在子图中。

设密度为 g g g
即为 g = ∑ e ∈ E x e ∑ v ∈ V x v g =\frac{\sum_{e\in E}x_e}{\sum_{v\in V}x_v} g=vVxveExe
变形:
∑ e ∈ E x e = ∑ v ∈ V x v g ⇔ ∑ e ∈ E x e − ∑ v ∈ V x v g = 0 \sum_{e\in E}x_e = {\sum_{v\in V}x_v} g \\ \Leftrightarrow \sum_{e\in E}x_e - {\sum_{v\in V}x_v} g = 0 eExe=vVxvgeExevVxvg=0

我们构造 h ( g ) = ∑ e ∈ E ′ x e − ∑ v ∈ V ′ g ⋅ x v h(g) = \sum_{e \in E'}x_e - \sum_{v \in V'}g \cdot x_v h(g)=eExevVgxv

若最优密度为 D D D , 我们可以得到:

{ h ( g ) < 0 g > D h ( g ) = 0 g = D h ( g ) > 0 g < D \begin{cases}h(g)<0 & g>D \\ h(g)=0 & g=D\\ h(g)>0 & g<D\end{cases} h(g)<0h(g)=0h(g)>0g>Dg=Dg<D

所以,问题就转化为了求一个 g g g ,使得 h ( g ) = 0 h(g) = 0 h(g)=0

很明显我们可以二分答案 g g g , 可以发现对于每条边 e ( u , v ) ∈ E e(u,v)\in E e(u,v)E,要加入子图中的前提是 ( u , v ) (u,v) (u,v) 都在图中,所以容易发现这可以用最大权闭合图来解决

  • 每条边作为一个点,向这条边的两个端点连 I N F INF INF 的边
  • 源点向所有原图中的边连边,容量为 1 1 1
  • 原图中的点向汇点连边,容量为 g g g

代码

#include <bits/stdc++.h>
using namespace std;
#define N 110
#define M 1500 
#define INF 1e10
const double eps = 1e-8;

struct edge{
    int u, v;
}edges[M];
int n, m, S, T;
int deg[N];

struct node{
    int to, nxt;
    double w;
}edge[M];
int head[N], tot;

void add(int u, int v, double c1, double c2){
    edge[tot].to=v, edge[tot].w=c1, edge[tot].nxt=head[u], head[u]=tot++; 
    edge[tot].to=u, edge[tot].w=c2, edge[tot].nxt=head[v], head[v]=tot++; 
}

void build(double g){
    memset(head, -1, sizeof head), tot=0;
    for(int i=1; i<=m; i++) add(edges[i].u, edges[i].v, 1, 1);
    for(int i=1; i<=n; i++) add(S, i, m, 0), add(i, T, m+2*g-deg[i], 0);
}

int dep[N], cur[N];
bool bfs(){
    memset(dep, -1, sizeof dep);
    queue<int> Q;
    Q.push(S);
	dep[S] = 0;
	cur[S] = head[S];

    while(Q.size()){
        int u = Q.front();
        Q.pop();
        for(int i = head[u]; ~i; i = edge[i].nxt){
            int v = edge[i].to;
            if(dep[v] == -1 && edge[i].w > eps){
                dep[v] = dep[u]+1;
                cur[v] = head[v];
                if(v == T) return true;
                Q.push(v); 
            }
        }
    }
    return false;
}

double find(int u, double limit){
    if(u == T){
    	return limit;
	}
    double flow = 0;
    for(int i = cur[u]; ~i && flow < limit; i = edge[i].nxt){
        int v = edge[i].to;
        cur[u] = i;
        if(dep[v] == dep[u] + 1 && edge[i].w > eps){
            double t = find(v, min(limit - flow, edge[i].w));
            if(t < eps) dep[v] = -1;
            edge[i].w -= t;
			edge[i ^ 1].w += t;
			flow += t;
        }
    }
    return flow;
}

double dinic(double g){
    build(g);
    double res = 0, flow;
    while(bfs()){
    	while(flow = find(S, INF)){
    		res += flow;
		}
	}
    return res;
} 

int res = 0;
bool vis[N];
void dfs(int u){
    vis[u] = true;
    if(u != S) res ++ ;
    for(int i = head[u]; ~i; i = edge[i].nxt){
        int v = edge[i].to;
        if(edge[i].w > 0 && !vis[v]) dfs(v);
    }
}

int main(){
    scanf("%lld %lld", &n, &m);

    S = 0;
	T = n + 1;
    for(int i = 1;i <= m;i ++ ){
        int u, v;
		scanf("%lld %lld", &u, &v);
        edges[i] = {u, v};
        deg[u] ++ ;
		deg[v] ++ ;
    }
    double l = 0, r = m;
    while(l + eps < r){
        double mid = (l + r) / 2;
        if(m * n - dinic(mid) > eps) l = mid;
        else r = mid;
    }

    dinic(l);
    dfs(S);

    if(!res){
        puts("1\n1");
        return 0;
    }

    cout<<res<<endl;
    for(int i = 1; i <= n; i++)
        if(vis[i]) printf("%d\n",i);
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值