poj 2125 二分图最小点权覆盖

给你一个有向图,每个点有两种操作,一种是删除它的所有入边,一种是删除它的所有出边,并且每个删除都有一个花费,w- 是出边的花费 ,w+ 是入边的花费 ; 让你求最小花费使得这个图删除所有的边。
先看一下下面几个定义:
点覆盖集:是无向图的一个点集,使得该图中所有边都至少有一个端点在该集合内。
最小点覆盖集:在无向图中,点数最少的点覆盖集。
最小点权覆盖集:是在带点权无向图中,点权之和最小的点覆盖集。
此题无疑求得就是最小点权覆盖集。
二分图的最小点权覆盖算法:建立一个超级源点s,向X部每个点连边,容量为X部每个点的点权;建立一个汇点t,从Y部每个点向汇点t连边,容量为Y部每个点的点权,把二分图中的边看成是有向的,u到v连接,容量为inf。则任意一条从s到t的路径,一定具有s->u->v->t。的形式。
此题就可以转换成求二分图的最小点权覆盖,将每个点拆点,拆成a和a',那么源点与a相连,容量为W-,a‘ 与汇点相连,容量为W+,如果u,v之间有连边,那么u和 v' 相连,容量为inf,跑一遍最大流,就是最小点权覆盖。
(因为要有s->->t的路径,所以是s和u的容量为w- ,w-代表出边 ,w+代表入边 ,所以 v`到t容量为w+)
然后输出:
从s开始搜索,我们知道最小割边集 都是那些跑完最大流后 边的容量为0的边 ; 要么割的是s到u的边 , 此时1到n不能搜索到点就是选择删掉出边的点,要么割的是u`到t的边,此时n+1到2*n能搜到的点就是就是选择删除掉入边的点。


#include<cstdio>
#include<cstring>
#include<map>
#include<vector>
#include<cmath>
#include<cstdlib>
#include<stack>
#include<queue>
#include <iomanip>
#include<iostream>
#include<algorithm>
using namespace std ;
const int N= 300 ;
const int M=50000 ;
const int inf=1<<30 ;
struct node
{
	int u,v,c,next ;
}edge[M] ;
int head[N],pre[N],cur[N],gap[N],dis[N],vist[N] ;
int top ;

void add(int u ,int v,int c)       
{      
    edge[top].u=u;      
    edge[top].v=v;      
    edge[top].c=c;      
    edge[top].next=head[u];      
    head[u]=top++;      
    edge[top].u=v;      
    edge[top].v=u;      
    edge[top].c=0;      
    edge[top].next=head[v];      
    head[v]=top++;      
}  
int sap(int s,int t ,int nv)    
{    
    memset(dis,0,sizeof(dis));    
    memset(gap,0,sizeof(gap));    
    int u,v,minflow=inf,flow=0;    
    for(int i = 0 ; i <nv ; i++)  cur[i]=head[i] ;    
    u=pre[s]=s;    
    gap[s]=nv;    
    while(dis[s] < nv )    
    {    
            loop :    
               for(int &j=cur[u] ; j!=-1 ; j=edge[j].next)                   
               {    
                     v=edge[j].v ;    
                     if(edge[j].c > 0 && dis[u] == dis[v] +1 )    
                     {    
                              minflow = min(minflow ,edge[j].c) ;    
                              pre[v]=u;    
                              u=v ;    
                          if(v==t)    
                          {      
                                for( u =pre[v] ; v!=s ;v=u,u=pre[u])    
                                      {    
                                            edge[cur[u]].c -= minflow ;     
                                            edge[cur[u]^1].c += minflow ;    
                                      }     
                                flow += minflow ;    
                                minflow = inf ;    
                          }    
                         goto loop ;    
                     }    
               }    
           int mindis=nv ;    
           for(int i = head[u] ; i!=-1 ; i=edge[i].next)    
           {    
                  v=edge[i].v ;    
                  if(edge[i].c > 0 && dis[v] < mindis)    
                  {    
                       mindis = dis[v] ;    
                       cur[u]=i ;           
                 }    
           }    
          if(--gap[dis[u]]==0) break ;    
          gap[ dis[u] = mindis +1 ]++  ;    
          u=pre[u] ;    
    }    
    return flow ;    
}  


void dfs(int u)
{
	vist[u] = 1;
	for(int i = head[u] ; i!=-1 ; i=edge[i].next)
	{
		  int  v = edge[i].v ;
		  if(!vist[v] && edge[i].c) 
		        dfs(v) ; 
	}
}

int main()
{
	int n,m ,x,u,v;
	while(~scanf("%d%d",&n,&m))
	{
		   top=0 ;
		   memset(head,-1,sizeof(head)) ;
		   int s=0,t=2*n+1 ;
		   for(int i = 1 ; i <= n ; i++)
		   {
		   	      scanf("%d",&x) ;
		   	      add(i+n,t,x) ;  //入边 
		   }
		   for(int i = 1 ; i <= n ; i++)
		   {
		   	     scanf("%d",&x) ;
		   	     add(s,i,x) ;//出边 
		   } 
		   
		   for(int i = 1 ; i <= m ; i++)
		   {
		          scanf("%d%d",&u,&v) ;
		          add(u,v+n,inf) ;
		   } 
		   int ans = sap(s,t,t+1) ;
		   printf("%d\n",ans) ;
		   memset(vist,0,sizeof(vist)) ;
		   dfs(s) ;
		   int sum = 0;
		   for(int i = 1 ; i <= n ; i++)
		   {
		   	       if(!vist[i]) sum++; //割边s->i 
		   	       if(vist[i+n]) sum++; //割边i+n->t ; 
		   }
		   printf("%d\n",sum) ;  //割边数量 
		  for(int i = 1 ; i <= n ;i++)
		  {
		  	    if(!vist[i])
		  	       printf("%d -\n" ,i ) ;
		  	    if(vist[i+n])
		  	       printf("%d +\n", i) ;
		  }
		
	}
	return 0;
} 





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值