poj3155 Hard Life 最小割 最大密度子图

题意简述:一个公司有n个人,给出了一些有冲突的人的对数(u,v),所有为了公司更好的发展,公司的总裁决定裁人,那么总裁现在裁要裁掉冲突率最高的哪些(冲突率=人数/在这些人中存在的冲突数

分析: 很明显的一个求最大密度子图的题目,求最大密度子图的方法有两种不同的模型可以求解,一种是采用了转换为最大权闭合图的模型来求解,而另一种则是通过补集转换的思想来求解,现在就最大权闭合图的模型来谈论以下:设max g = f(x)= |E‘|/|V’| ,找一个子图的边数与点数的比值达到其中的最大,我们通常都是构造一个函数max h(g)= |E'|-g*|V'|,当h(g)为0的时候,g的值即为最优(证明见Amber论文),h(g)>0 时 g<最优值, h(g)<0时,g>最优值; 那么首先来解释为什么要是该函数的值尽量大?因为如果最大值大于0那么我们就可以继续增加g的值来减小h(g),若最大值都小于0了,那么g不可能增加只可能减少!

注意观察h(g),边和点有依赖关系,就边依赖点,边存在的必要条件是点的存在,那么这样以后,如果我们将边看成点,那么这不就符合最大权闭合子图了么,现在h(g)的求法就可以通过求新图的最大权闭合子图的值来求解,但是这里有个问题,建图之后你可以发现当求出来的值和h(g)原本应该为值不对应(具体为什么不怎么理解),可以这样理解,当最小的一个g使得h(g)为0的时候该解即为最优解,因为h(g)是以个单调递减函数,就该函数来看只可能存在一个g使得h(g)=0;然而通过求最大权闭合子图是子图权值和为0的有很多中g,当最小的一个g使得h(g)为0之后,如果g继续增大那么虽然通过最大权闭合子图的值求出来依旧为0,但是真正的h(g)< 0 了,所以要使得最优的一个解就是使得最大权闭合子图的权值和为0的最小的一个g值!这样求解之后从点点流到汇点为满流的边即为最大密度子图中的点

注意精度的控制!


第二种模型的建图: 源点到各个点连接一条有向边权值为U,各个点到汇点连接一条边权值为U+2*g-d,原来有关系的点连接两条有向边(u,v),(v,u)权值为1(U可以取m,U的目的是用来使得2*g-d的值始终为正),这样以后求最小割,那么h(g)= (U*n-mincut)/2;二分找到最优值即为mid ,但是如果要求图中的点则需要用left来从新图求最大流之后然后从源点开始dfs遍历,最后得出结果

题目的具体解法大家可以参考胡伯涛的论文《最小割模型在信息学竞赛中的应用》,论文中给出了详细的分析。

根据discuss里面的大牛说的:

这里再补充一点编程时需要注意的地方:这个题目是利用分数规划进行二分求解的,但这个问题的分数规划和我之前的见过的很多分数规划是不同的,之前的分数规划表达式很多都是在给定区间内只有一个零点的单调函数。但这个题目不同,因为MiniCut=U*n-Maxmize{f(n)},而MiniCut& lt;=U*n是恒成立的,所以Maxmize{f(n)}>=0恒成立,及Maxmize{f(n)}函数会先递减,然后一直为0,我们的目标是找出第一个零点。

所以二分应该这么写:

while(r-l >= 1.0/n/n)
{
    mid = (l+r) / 2;
    BuildGraph();
    Cut = Max_flow(s, t);
    tmp = (U*n-Cut) / 2;
    if(tmp > eps) l = mid;
    else r = mid;
}

而不能当找出一个零点时直接break。

第二个需要注意的地方:对于这种输入数据:

3 3

1 2

2 3

3 1

最后一次网络流的运行会使所有从s出发的边满流,这时候子图的顶点数为0,结果输出为0,不符合题意。但是由于误差最多不会超过1/(n*n),所以取l再做一次网络流然后查解输出就可以了(解就是所有从s通过最后的参量网络可达的顶点——因为这保证了不走割边,这就和论文中的讲解一致了)。(这也就是我的代码中为什么在二分后还sap一次的原因)

#include<iostream> 
#include<cstdio>
#include<cstring> 
#include<cmath> 
using namespace std;   
#define MAXN 500 
#define MAXE 40000 
#define INF 1e8 
#define EPS 1e-7
int mark[MAXN];
int x[MAXN],y[MAXN],deg[MAXN];
int g[MAXN][MAXN];
double c[MAXN];
int n,m;
int nv,ne;
int s,t,idx;
double low,hig,flow,flows,mid;
	
struct Edge{ 
    int next,pair; 
    int v;
	double cap,flow; 
}edge[MAXE]; 
int net[MAXN]; 
double sap() 
{ 
    int numb[MAXN],dist[MAXN],curedge[MAXN],pre[MAXN]; 
    int u,tmp,neck,i; 
   	double cur_flow,max_flow;
    memset(dist,0,sizeof(dist)); 
    memset(numb,0,sizeof(numb)); 
    memset(pre,-1,sizeof(pre)); 
    for(i = 1 ; i <= nv ; ++i) 
        curedge[i] = net[i]; 
    numb[nv] = nv; 
    max_flow = 0; 
    u = s; 
    while(dist[s] < nv) 
    { 
        if(u == t) 
        { 
            cur_flow = INF; 
            for(i = s; i != t;i = edge[curedge[i]].v)  
            {   
                if(cur_flow > edge[curedge[i]].cap+EPS ) 
                { 
                    neck = i; 
                    cur_flow = edge[curedge[i]].cap; 
                } 
            } 
            for(i = s; i != t; i = edge[curedge[i]].v) 
            { 
                tmp = curedge[i]; 
                edge[tmp].cap -= cur_flow; 
                edge[tmp].flow += cur_flow; 
                tmp = edge[tmp].pair; 
                edge[tmp].cap += cur_flow; 
                edge[tmp].flow -= cur_flow; 
            } 
            max_flow += cur_flow; 
            u = neck; 
        } 
        for(i = curedge[u]; i != -1; i = edge[i].next) 
            if(edge[i].cap > EPS && dist[u] == dist[edge[i].v]+1) 
                break; 
        if(i != -1) 
        { 
            curedge[u] = i; 
            pre[edge[i].v] = u; 
            u = edge[i].v; 
        }else{ 
            if(0 == --numb[dist[u]]) break; 
            curedge[u] = net[u]; 
            for(tmp = nv,i = net[u]; i != -1; i = edge[i].next) 
                if(edge[i].cap > EPS) 
                    tmp = tmp<dist[edge[i].v]?tmp:dist[edge[i].v]; 
            dist[u] = tmp + 1; 
            ++numb[dist[u]]; 
            if(u != s) u = pre[u]; 
        } 
    }      
    return max_flow; 
} 
void addedge(int u,int v,double cl,double cl2=0)
{
	edge[idx].v=v;
	edge[idx].cap=cl;
	edge[idx].flow=0;
	edge[idx].next=net[u];
	edge[idx].pair=idx+1;
	net[u]=idx++;
	edge[idx].next=net[v];
	edge[idx].v=u;
	edge[idx].cap=cl2;
	edge[idx].flow=0;
	edge[idx].pair=idx-1;
	net[v]=idx++;
}
void init()
{
	memset(net,-1,sizeof(net));
	idx=0;
	s=0;
	t=n+1;
	nv=t+1;
}
double binary()
{
	int i,j;
	low=0;hig=m;
	double bt=1.0/n/n;
	//low+EPS<=hig
	//hig-low>=bt
	while(hig-low>=bt)//二分搜索 
	{
		mid=(low+hig)*0.5;
		flow=0;
		init();
		for(i=1;i<=n;i++)
		{
			//起始点和终点的反向权应该为0! 
			addedge(0,i,m);
			addedge(i,n+1,m+2*mid-(double)deg[i]);
		}
		for(i=1;i<=n;i++)
			for(j=1;j<=n;j++)
			//此处在建立反向权边的时候里面是正权值!!
			if(g[i][j])addedge(i,j,1.0,1.0);
		
		flow=sap();
		flows=(m*n-flow)*0.5;
		if(flows>EPS)
		{
			low=mid;
		}
		else
		{
			hig=mid;
		}		
	}
	return mid;
}

void dfs(int u)
{
	for(int i=net[u];i!=-1;i=edge[i].next)//啊啊啊啊啊啊啊 
	{
		if(edge[i].cap>EPS && !mark[edge[i].v])
		{
			mark[edge[i].v]=1;
			dfs(edge[i].v);
		}
	}
}
void solve()
{
	int i,j,first;
	double rat=binary();//二分得到最小比率值 
	memset(mark,0,sizeof(mark));
	mark[s]=1;
	init();
	//cout<<low<<endl;
		for(i=1;i<=n;i++)
		{
			addedge(0,i,m);
			addedge(i,n+1,m+2*low-deg[i]);
		}
		for(i=1;i<=n;i++)
			for(j=1;j<=n;j++)
			//此处在建立反向权边的时候里面是正权值!! 
			if(g[i][j])addedge(i,j,1.0,1.0);
		
	sap();		
	dfs(0);//深搜找割 
	int ans=0;
	for(i=1;i<=n;i++) 
	{
		if(mark[i])
		{
			ans++;
		}
	}
	printf("%d\n",ans);
	for(i=1;i<=n;i++)
	{
		if(mark[i])
		{
			printf("%d\n",i);
		}
	}
	//puts("");
}
int main()
{
	int i;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		int tmpx,tmpy;
		memset(deg,0,sizeof(deg));
		memset(g,0,sizeof(g));
		for(i=0;i<m;i++)
		{
			scanf("%d%d",&tmpx,&tmpy);
			g[tmpx][tmpy]=1;
			deg[tmpx]++;
			deg[tmpy]++;
		}
		if(m == 0)
        {
            printf("1\n1\n");
            continue;
        }

		ne=m;
		nv=n+2;
		solve();
	}
	return 0;
}

二分的精度问题。因为可以证明任两个最大密度子图比率不小于1/(n^2)。所以说这个可以作为二分精度。但是推至带权之后不具此性质。

这题特别注意。二分结束后要用所得g值重新构图跑最大流。不然可能得不到正确结果。



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值