UVA1389 题解
LittleMoMol · · 题解
UVA1389 Hard Life 题解
前言
由于这道题的建模很简单,所以在这里我们重点讨论一下最大密度子图的模板原理
问题描述
最大密度子图是这样一类问题:
给定一张图,选择其中的一个子图,使得该子图的点数除以边数的值最大。选择的子图必须满足:若一条边被选上,则其两个端点必须选;若一个点被选上,与其相邻的边可以不选,不做要求。(这个很好理解,图越密的话边数越多)
原图记为
总框架
对于分数的最值,通常会用 01 分数规划来解决,而对于 01 分数规划问题,通常用二分来解决。
那么我们可以二分枚举
反之,同理。
接下来考虑左边界和右边界,对于左边界,由于都是正数运算,故
对于每个
重点:求值
设想一下,如果某个子图已经确定了,也就是
诱导子图:选出来的子图,满足点与点的连边都在子图中,形式化地,对于子图
接着,要将诱导子图的边一个个找出来。
正难则反,可以用所选点的度数-所选点集的补集与这个点集所连的边来求出。
在这个图中,若选择的子图为
统计所选点的度数很简单,但后者就不易了,如上图,对于红色的边,它像什么?它是不是非常像最小割!对叭~那么就来考虑最小割!
因为是最小割,我们就把最大化
接下来,我们看一看
令
我们想让原问题和最小割一一对应起来,但不幸的是,除了最小割,还多了一项让人讨厌的
我们可以这样做
- 将所有
v \in V 向汇点T 连一条权值为2g-d_v+U 的边(U 为常数,来确保权值非负) - 若
u,v,e_{u,v} \in G ,则u 向v 连一条容量为1 的边 - 源点
S 向u \in V 连一条容量为U 的边
为什么这样建图?暂且不管。建完后,我们看一看
令
因为
这也正是这样建图的妙处,一一对应上了!
最终,我们得到了
好耶!接下来就是敲代码啦!
Code
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110, M = (1000 + N * 2) * 2, INF = 1e8;
int n, m, S, T;
int h[N], e[M], ne[M], idx;
double f[M];
int q[N], depth[N], cur[N];
int dg[N];
struct warma
{
int a, b;
} edges[M];
int ans;
bool st[N];
void add(int a, int b, double c1, double c2)
{
e[idx] = b;
f[idx] = c1;
ne[idx] = h[a];
h[a] = idx ++ ;
e[idx] = a;
f[idx] = c2;
ne[idx] = h[b];
h[b] = idx ++ ;
return;
}
void build(double g)
{
memset(h, -1, sizeof h);
idx = 0;
for (int i = 0; i < m; i ++ )
add(edges[i].a, edges[i].b, 1, 1);
int U = m;
for (int i = 1; i <= n; i ++ )
{
add(S, i, U, 0);
add(i, T, U + 2 * g - dg[i], 0);
}
}
bool bfs()
{
int hh = 0, tt = 0;
memset(depth, -1, sizeof depth);
q[0] = S;
depth[S] = 0;
cur[S] = h[S];
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i])
{
int ver = e[i];
if (depth[ver] == -1 && f[i] > 0)
{
depth[ver] = depth[t] + 1;
cur[ver] = h[ver];
q[ ++ tt] = ver;
if (ver == T) return true;
}
}
}
return false;
}
double dfs(int u, double lmt)
{
if (u == T) return lmt;
double flow = 0;
for (int i = cur[u]; ~i; i = ne[i])
{
cur[u] = i;
int ver = e[i];
if (depth[ver] == depth[u] + 1 && f[i] > 0)
{
double t = dfs(ver, min(f[i], lmt - flow));
if (t <= 0) depth[ver] = -1;
f[i] -= t;
f[i ^ 1] += t;
flow += t;
}
}
return flow;
}
double dinic(double g)
{
build(g);
double res = 0, flow = 0;
while (bfs())
while (flow = dfs(S, INF))
res += flow;
return res;
}
void way(int u)
{
st[u] = true;
if (u != S) ans ++ ;
for (int i = h[u]; ~i; i = ne[i])
{
int ver = e[i];
if (!st[ver] && f[i] > 0)
way(ver);
}
return;
}
int main()
{
while (cin >> n >> m)
{
//cin >> n >> m;
S = 0, T = n + 1;
memset(dg, 0, sizeof dg);
memset(st, 0, sizeof st);
ans = 0;
for (int i = 0; i < m; i ++ )
{
int a, b;
cin >> a >> b;
dg[a] ++ ;
dg[b] ++ ;
edges[i] = {a, b};
}
double L = 0, R = m;
int U = m;
while (R - L > 1e-8)
{
double mid = (L + R) / 2;
double t = dinic(mid);
if (U * n - t > 0) L = mid;
else R = mid;
}
dinic(L);
way(S);
if (!ans) cout << 1 << endl << 1 << endl;
else
{
cout << ans << endl;
for (int i = 1; i <= n; i ++ )
if (st[i])
cout << i << endl;
}
cout << endl;
}
return 0;
}
后语
写这篇题解前我还是不太理解最大密度子图的蒟蒻,而写题解的过程中通过看论文、视频等等方式,肝了一夜,终于完成了题解并大致理解了!希望大家也多多通过这种方式学习,真的很有效果呢~