0-1分数规划 网络流 最小割 最大流最小割 zoj2676 network



【题目大意】
选取一边集E,使得边集的平均值最小
注意边集中必须包含割(题目要求)

于是 分数规划,二分. 对于二分的每一个 mid , 将 weight <= mid的 添加入临时解集,并对于剩下的网络(weight -= mid)求一个最小割,然后所求的集合便是mid 条件下的边集

最后输出边集既可
一个无向图,求最小的平均割:,其中,wi表示边权,该表达式的意思就是选择某些边集构成S-T割,使得平均割最小。

分析:0-1分数规划问题,设,则令mincut=因为ci只能取0或1所以转换模型,边权值变为,每条边取与不取使得取到的边集成为一个最小S-T割,因为这个0-1分数规划满足单调性且当且仅当mincut=0时取到最小值,mincut<0时小于最优解,mincut>0时大于最优解,所以可以二分的值,使得min=0。这里对于的边一定是可取的,因为这些边只会减小最小割所以一定是可取的。最后二分的时候注意精度。

 

可以参见胡伯涛的论文

学0-1规划慢慢细细的学了2天真是太失败了。。。

 

#include<iostream> 
#include<cstdio>
#include<cstring> 
#include<memory.h> 
#include<cmath> 
using namespace std;   
#define MAXN 500 
#define MAXE 40000 
#define INF 0x7fffffff 
#define EPS 1e-6
int mark[MAXN];
int x[MAXN],y[MAXN];
double c[MAXN];
int n,m;
int nv,ne;
int s,t,idx;
struct Edge{ 
    long next,pair; 
    long v;
	double cap,flow; 
}edge[MAXE]; 
long net[MAXN]; 
double sap() 
{ 
    long numb[MAXN],dist[MAXN],curedge[MAXN],pre[MAXN]; 
    long 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) 
    { 
        /* first , check if has augmemt flow */
        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; 
        } 
        /* if .... else ... */
        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 add(int u,int v,double cl)
{
	idx++;
	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=cl;
	edge[idx].flow=0;
	edge[idx].pair=idx-1;
	net[v]=idx++;
}
void init()
{
	memset(net,-1,sizeof(net));
	idx=0;
	s=1;
	t=n;
}
double binary()
{
	double low,hig,mid,flow;
	int i;
	low=0;hig=10000001;
	while(low+EPS<hig)//二分搜索 
	{
		mid=(low+hig)/2;
		flow=0;
		init();
		for(i=1;i<=m;i++)
		{
			if(c[i]+EPS<mid)
			{
				flow+=c[i]-mid;
			}
			else
			{
				add(x[i],y[i],c[i]-mid);
			}
		}
		flow+=sap();
		if(flow>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,first;
	double rat=binary();//二分得到最小比率值 
	memset(mark,0,sizeof(mark));
	mark[s]=1;
	dfs(s);//深搜找割 
	int ans=0;
	/*后面是输出答案,用mark数组标记,如果c[i]在二分最小比率值一下  
	且mark两个点中存在一个值为1则统计 */ 
	for(i=1;i<=m;i++) 
	{
		if(c[i]+EPS < rat || (mark[x[i]]+mark[y[i]])==1)
		{
			ans++;
		}
	}
	printf("%d\n",ans);
	first=1;
	for(i=1;i<=m;i++)
	{
		if(c[i]+EPS < rat || (mark[x[i]]+mark[y[i]])==1)
		{
			if(first)
			{
				printf("%d",i);
				first=0;
			}
			else
			printf(" %d",i);
		}
	}
	puts("");
}
int main()
{
	int i;
	int first=1;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		if(!first)puts("");
		first=0;
		for(i=1;i<=m;i++)
		{
			scanf("%d%d%lf",&x[i],&y[i],&c[i]);
		}
		ne=m;
		nv=n;
		solve();
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值