UVa1389/LA3709 Hard Life

题目链接

  本题是2006年icpc欧洲区域赛东北欧赛区H

题意

  约翰是一家公司的CEO。公司的股东决定让他的儿子斯科特成为公司的经理。约翰十分担心,儿子会因为在经理岗位上表现优异而威胁到他CEO的位置。因此,他决定精心挑选儿子要管理的团队人员,让儿子知道社会的险恶。已知公司中一共有n(1≤n≤100)名员工,员工之间共有m(0≤m≤1000)对两两矛盾关系。如果将一对有矛盾的员工安排在同一个团队,那么团队的管理难度就会增大。一个团队的管理难度系数等于团队中的矛盾关系对数除以团队总人数。团队的管理难度系数越大,团队就越难管理。约翰希望给儿子安排的团队的管理难度系数尽可能大。
Hard Life
  以上图为例,管理难度系数最大的团队由1,2,4,5号员工组成,他们4人中共有5对矛盾关系,所以管理难度系数为5/4。如果将3号员工也加入到团队之中,那么管理难度系数就会降至6/5。

输入格式

  第一行包含两个整数n和m。接下来m行,每行包含两个整数 a i a_i ai b i b_i bi,表示员工 a i a_i ai b i b_i bi之间存在矛盾。所有员工编号从 1 到 n。每个矛盾对最多在输入中出现一次,且介绍矛盾对时,员工介绍顺序是随意的。

输出格式

  首先输出一个整数 k,表示安排给斯科特的团队人员数量。接下来 k 行,以升序输出团队每个成员的编号,每行一个。如果答案不唯一,则输出任意一种即可。
  注意:至少要选择一名员工。

分析

  最大密度子图,相关概念和求解算法详细的分析参考以下两个博客:
  【学习笔记】最大密度子图
  最大密度子图的相关概念

  最大密度子图是一种分数规划问题,需要用二分法求解。二分找零点的过程需要建图求最小割,可以转化为最大权闭合子图来求,不过有更好的办法:对原图的边 ( u , v ) (u,v) (u,v) u u u v v v连一条容量为1的边(反向边容量也是1);源点 s s s向每个结点 x x x连容量为 m m m(原图边数)的有向边;结点 x x x向汇点 t t t连容量为 2 λ − d x + m 2\lambda-d_x+m 2λdx+m的边。
   c ( u , v ) = 1 c ( s , x ) = m c ( x , t ) = 2 λ − d x + m \displaystyle \begin{aligned} c(u,v)&=1\\ c(s,x)&=m\\ c(x,t)&=2\lambda-d_x+m \end{aligned} c(u,v)c(s,x)c(x,t)=1=m=2λdx+m

  注意:由于二分过程中 λ \lambda λ是小数,建图时边的容量和流量要定义成double;任意两个子图的密度差不小于 1 n 2 \frac{1}{n^2} n21,因此可将 l + e p s < r l+eps<r l+eps<r e p s eps eps赋值为 1 n 2 \frac{1}{n^2} n21

  如果要输出方案,根据所采用的最大流算法求出S点集和T点集,S点集去掉源点就是最大密度子图的点集V,进一步如果要输出边就是那些两个端点都在V中的边。

AC 代码

#include <iostream>
#include <cstring>
using namespace std;

#define M 2440
#define N 110
struct edge {int u, v; double cap, flow;} e[M];
int g[N][M>>1], q[N], p[N], d[N], cur[N], num[N+1], cnt[N], deg[N], c, m, n, kase = 0; bool vis[N];

void add_edge(int u, int v, double cap, double cc = 0.) {
    e[c] = {u, v, cap, 0}; g[u][cnt[u]++] = c++; e[c] = {v, u, cc, 0}; g[v][cnt[v]++] = c++;
}

bool bfs(int s, int t) {
    memset(vis, 0, sizeof(vis)); q[0] = t; d[t] = 0; vis[t] = true;
    int head = 0, tail = 1;
    while (head < tail) {
        int v = q[head++];
        for (int i=0; i<cnt[v]; ++i) {
            const edge& ee = e[g[v][i]^1];
            if (!vis[ee.u] && ee.cap > ee.flow) vis[ee.u] = true, d[ee.u] = d[v] + 1, q[tail++] = ee.u;
        }
    }
    return vis[s];
}

double max_flow(int s, int t, double x) {
    double flow = 0.;
    for (int i=0; i<c; ++i) e[i].flow = 0.;
    for (int i=0; i<cnt[t]; ++i) {
        edge &ee = e[g[t][i]^1];
        ee.cap = 2.*x + m - deg[ee.u];
    }
    for (int i=0; i<=t; ++i) d[i] = t+1;
    if (!bfs(s, t)) return 0.;
    memset(num, 0, sizeof(num)); memset(cur, 0, sizeof(cur));
    for (int i=0; i<=t; ++i) ++num[d[i]];
    for (int u=s; d[s] <= t;) {
        if (u == t) {
            double a = 1e30;
            for (int v=t; v!=s; v = e[p[v]].u) a = min(a, e[p[v]].cap - e[p[v]].flow);
            for (int v=t; v!=s; v = e[p[v]].u) e[p[v]].flow += a, e[p[v]^1].flow -= a;
            flow += a; u = s;
        }
        bool ok = false;
        for (int i=cur[u]; i<cnt[u]; ++i) {
            const edge& ee = e[g[u][i]];
            if (ee.cap > ee.flow && d[u] == d[ee.v] + 1) {
                ok = true; p[ee.v] = g[u][i]; cur[u] = i; u = ee.v;
                break;
            }
        }
        if (!ok) {
            int m = t;
            for (int i=0; i<cnt[u]; ++i) {
                const edge& ee = e[g[u][i]];
                if (ee.cap > ee.flow) m = min(m, d[ee.v]);
            }
            if (--num[d[u]] == 0) break;
            ++num[d[u] = m + 1]; cur[u] = 0;
            if (u != s) u = e[p[u]].u;
        }
    }
    return flow;
}

void solve() {
    if (kase++) cout << endl;
    int s = 0, t = n+1, cc = 0; memset(cnt, c = 0, sizeof(cnt)); memset(deg, 0, sizeof(deg));
    for (int i=0; i<m; ++i) {
        int u, v; cin >> u >> v; ++deg[u]; ++deg[v]; add_edge(u, v, 1, 1);
    }
    for (int i=1; i<=n; ++i) add_edge(s, i, m), add_edge(i, t, m);
    double l = 0., r = m, eps = 1./n/n, f = m*n;
    while (l+eps < r) {
        double x = .5*(l+r);
        max_flow(s, t, x) < f ? l = x : r = x;
    }
    max_flow(s, t, l);
    bfs(s, t);
    for (int i=1; i<=n; ++i) if (!vis[i]) ++cc;
    cout << cc << endl;
    for (int i=1; i<=n; ++i) if (!vis[i]) {
        cout << i << endl;
        if (--cc == 0) return;
    }
}

int main() {
    while (cin >> n >> m) solve();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值