POJ 3155 Hard Life(最大密度子图)

87 篇文章 0 订阅

Hard Life
Time Limit: 8000MS Memory Limit: 65536K
Total Submissions: 9111 Accepted: 2648
Case Time Limit: 2000MS Special Judge
Description

John is a Chief Executive Officer at a privately owned medium size company. The owner of the company has decided to make his son Scott a manager in the company. John fears that the owner will ultimately give CEO position to Scott if he does well on his new manager position, so he decided to make Scott’s life as hard as possible by carefully selecting the team he is going to manage in the company.

John knows which pairs of his people work poorly in the same team. John introduced a hardness factor of a team — it is a number of pairs of people from this team who work poorly in the same team divided by the total number of people in the team. The larger is the hardness factor, the harder is this team to manage. John wants to find a group of people in the company that are hardest to manage and make it Scott’s team. Please, help him.

In the example on the picture the hardest team consists of people 1, 2, 4, and 5. Among 4 of them 5 pairs work poorly in the same team, thus hardness factor is equal to 5⁄4. If we add person number 3 to the team then hardness factor decreases to 6⁄5.

Input

The first line of the input file contains two integer numbers n and m (1 ≤ n ≤ 100, 0 ≤ m ≤ 1000). Here n is a total number of people in the company (people are numbered from 1 to n), and m is the number of pairs of people who work poorly in the same team. Next m lines describe those pairs with two integer numbers ai and bi (1 ≤ ai, bi ≤ n, ai ≠ bi) on a line. The order of people in a pair is arbitrary and no pair is listed twice.

Output

Write to the output file an integer number k (1 ≤ k ≤ n) — the number of people in the hardest team, followed by k lines listing people from this team in ascending order. If there are multiple teams with the same hardness factor then write any one.

Sample Input

sample input #1
5 6
1 5
5 4
4 2
2 5
1 2
3 1

sample input #2
4 0
Sample Output

sample output #1
4
1
2
4
5

sample output #2
1
1
Hint

Note, that in the last example any team has hardness factor of zero, and any non-empty list of people is a valid answer.

Source

Northeastern Europe 2006

题目大意:

  有一张无向无权图,让你找出一个子图,使得子图中的边数/顶点数最大。

解题思路:

  最大密度子图的最简单情况,详细解法胡伯涛的《最小割模型在信息学中的应用》。这里只做简要说明。
  首先,最大密度子图是一个01分数规划问题,我们所使用的求解方法就是求解01分数规划问题的方法。
  记 V=|| E=|| n=|| m=|| ,那问题就变成了最大化 D=mn 。先确定二分的范围,答案一定属于 [1V,E1] (当原图不存在边时需要特判),接下来可以知道答案之间的距离一定不小于 1V×V 。对于当前的答案猜测值 g ,令h(g)=maxx{eE1×xevVg×xe}。为了求解当前的 h(g) ,我们可以建图利用最小割求解。
  建图方式如下:首先定义一个足够大的常量 U=E ,以避免接下来边的容量出现负数。在原图的基础上增加源点s和汇点t。对于原图每一条边建立两条容量为1的边 u,v v,u 。从原点s向每个原图节点u建立一条有向边 s,u ,容量为U。从每个原图节点向汇点建立一条有向边 u,t ,容量为 U2gdeg(u) ,其中 deg(u) 为原图中顶点u的度数,那么 h(g)=U×Vc[s,t]2 ,其中 c[s,t] 为最小割。选择的节点即为从s出发进过没有满流的边可以到达的点。
  这里有一个细节,由于我们建成的图的最大流一定不会超过 U×V ,所以本来应该 h(g) 小于0的时候通过这个方式得到的 h(g) 是等于0的,而这时也无法找到选择的点,所以和大多数分数规划不一样这里要把 h(g) 小于0和等于0一起处理。也因为当 h(g) 等于0的时候无法找到选择的点,所以我们就没有办法使用Dinkelbach算法。
  这个写法还可以向边带权的无向图,以及点边都带权的无向图扩展。
  对于边带非负权的无向图:答案之间不再有最小差值,我们需要规定二分的精度。常量U需要该为所有边权值之和。重新定义 deg(u) 为与u相连的边的权值之和。对于每条从原图替换得到的边的容量改为原图中这条边的权值。其它地方不变。
  对于边带非负权点带可正可负的权值的无向图:同样需要规定二分精度。常量U该为点权绝对值和的两倍加上边权和。同样重新定义 deg(u) 。从源图替换得到的边的容量同样改为权值。每个节点到汇点的边容量改为 U+2gdu2pu ,其中 pu 为节点u的权值。其它不变。

AC代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <vector>
#include <queue>
#include <stack>
#include <deque>
#include <string>
#include <map>
#include <set>
#include <list>
using namespace std;
#define INF 0x3f3f3f3f
#define LL long long
#define fi first
#define se second
#define mem(a,b) memset((a),(b),sizeof(a))

const double eps=1e-8;
const int MAXV=100+3;
const int MAXE=1000+3;

struct Edge
{
    int to, rev;
    double cap;
    Edge(int t, double c, int r):to(t), cap(c), rev(r){}
};

pair<int, int> edge[MAXE];
int N, V, E, U, cnt;
int deg[MAXV];//原图中点的度数
vector<Edge> G[MAXV];
int level[MAXV];
int iter[MAXV];
bool vis[MAXV];

void add_edge1(int from, int to, double cap)//建立有向边
{
    G[from].push_back(Edge(to, cap, G[to].size()));
    G[to].push_back(Edge(from, 0.0, G[from].size()-1));
}

void add_edge2(int from, int to, double cap)//建立双向边
{
    G[from].push_back(Edge(to, cap, G[to].size()));
    G[to].push_back(Edge(from, cap, G[from].size()-1));
}

void bfs(int s)
{
    mem(level, -1);
    queue<int> que;
    level[s]=0;
    que.push(s);
    while(!que.empty())
    {
        int u=que.front(); que.pop();
        for(int i=0;i<G[u].size();++i)
        {
            Edge &e=G[u][i];
            if(e.cap>eps && level[e.to]<0)
            {
                level[e.to]=level[u]+1;
                que.push(e.to);
            }
        }
    }
}

double dfs(int u, int t, double f)
{
    if(u==t)
        return f;
    for(int &i=iter[u];i<G[u].size();++i)
    {
        Edge &e=G[u][i];
        if(e.cap>0 && level[u]<level[e.to])
        {
            double d=dfs(e.to, t, min(f, e.cap));
            if(d>eps)
            {
                e.cap-=d;
                G[e.to][e.rev].cap+=d;
                return d;
            }
        }
    }
    return 0;
}

double dinic(int s, int t)//最大流dinic算法
{
    double flow=0;
    while(true)
    {
        bfs(s);
        if(level[t]<0)
            return flow;
        mem(iter, 0);
        double f;
        while((f=dfs(s, t, INF))>eps)
            flow+=f;
    }
}

void init()//初始化
{
    V=N+2;
    U=E;
    for(int i=0;i<V;++i)
    {
        deg[i]=0;
        vis[i]=false;
    }
    cnt=0;
}

double judge(double x)
{
    // 0     源点
    // 1 ~ N 原图节点
    // N+1   汇点
    int s=0, t=N+1;
    for(int i=0;i<V;++i)
        G[i].clear();
    for(int i=0;i<E;++i)
        add_edge2(edge[i].fi, edge[i].se, 1.0);
    for(int i=1;i<=N;++i)
    {
        add_edge1(s, i, U);
        add_edge1(i, t, U+2*x-deg[i]);
    }
    return (U*N-dinic(s, t))/2;
}

void dfs2(int u)//寻找可以通过没有满流的边到达的点
{
    vis[u]=true;
    ++cnt;
    for(int i=0;i<G[u].size();++i)
    {
        int v=G[u][i].to;
        if(!vis[v] && G[u][i].cap>0)
            dfs2(v);
    }
}

int main()
{
    while(~scanf("%d%d", &N, &E))
    {
        if(E==0)
        {
            puts("1\n1");
            continue;
        }
        init();
        for(int i=0;i<E;++i)
        {
            scanf("%d%d", &edge[i].fi, &edge[i].se);
            ++deg[edge[i].fi];
            ++deg[edge[i].se];
        }
        double l=1.0/N, r=E/1.0, min_dif=1.0/N/N;
        while(r-l>=min_dif)
        {
            double mid=(l+r)/2;
            if(judge(mid)>eps)
                l=mid;
            else r=mid;
        }
        judge(l);//对最终结果重新建图以找到选择的点
        dfs2(0);
        printf("%d\n", cnt-1);
        for(int i=1;i<=N;++i)
            if(vis[i])
                printf("%d\n", i);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值