HDU 2853 Assignment [网络流 最小费最大流]

本文介绍了一道关于最大流最小费的经典问题,通过调整建边方式解决变更次数限制,实现效益最大化的同时确保变更次数最小。文章提供了详细的解题思路及AC代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:有n个公司,m个任务,每个公司相对应每个任务有一个效益,每个公司最初有安排一个任务,求收获最大效益时候的最小变更次数。

题解:最大流最小费的题,但是建边与以往不同,每个任务和每个公司流量均为1,限制一个公司只能完成一个任务,一个任务只能由一个公司来完成。但是变更次数的限制很难完成,所以我们就设置每个公司连接的任务的费用为-N*效益,N代表最大的任务数,建边时,若是公司最初安排的任务,则这个公司连接这个任务的费用为-N*效益-1,流量为1,

若不是则费用为-N*效益,流量为1。这样建边,最后没有变更的数是-cost%N,得到的效益为-cost/N。

AC代码:

#include<cstdio>    
#include<cstring>    
#include<queue>    
#include<cmath>    
using namespace std;  
const   int oo=0x7f7f7f7f;  //无穷  
const   int mm=5505;  //边  
const   int mn=2505;  //点  
int node,src,dest,edge;    
int ver[mm],flow[mm],cost[mm],nex[mm];    
int head[mn],dis[mn],p[mn],q[mn],vis[mn];    
/**这些变量基本与最大流相同,增加了  
 cost 表示边的费用,  
 p 记录可行流上节点对应的反向边  
 */    
void prepare(int _node,int _src,int _dest)  //预处理   点的个数  起点  终点  
{    
    node=_node,src=_src,dest=_dest;    
    for(int i=0; i<node; i++)head[i]=-1,vis[i]=0;    
    edge=0;    
}    
void addedge(int u,int v,int f,int c)    
{    
    ver[edge]=v,flow[edge]=f,cost[edge]=c,nex[edge]=head[u],head[u]=edge++;    
    ver[edge]=u,flow[edge]=0,cost[edge]=-c,nex[edge]=head[v],head[v]=edge++;    
}    
/**以上同最大流*/    
/**spfa 求最短路,并用 p 记录最短路上的边*/    
bool spfa()    
{    
    int i,u,v,l,r=0,tmp;    
    for(i=0; i<node; ++i)dis[i]=oo;    
    dis[q[r++]=src]=0;    
    p[src]=p[dest]=-1;    
    for(l=0; l!=r; (++l>=mn)?l=0:l)    
        for(i=head[u=q[l]],vis[u]=0; i>=0; i=nex[i])    
            if(flow[i]&&dis[v=ver[i]]>(tmp=dis[u]+cost[i]))    
            {    
                dis[v]=tmp;    
                p[v]=i^1;    
                if(vis[v]) continue;    
                vis[q[r++]=v]=1;    
                if(r>=mn)r=0;    
            }    
    return p[dest]>-1;    
}    
/**源点到汇点的一条最短路即可行流,不断的找这样的可行流*/    
int SpfaFlow()    
{    
    int i,ret=0,delta;    
    while(spfa())    
    {    
        for(i=p[dest],delta=oo; i>=0; i=p[ver[i]])    
            if(flow[i^1]<delta)delta=flow[i^1];    
        for(i=p[dest]; i>=0; i=p[ver[i]])    
            flow[i]+=delta,flow[i^1]-=delta;    
        ret+=delta*dis[dest];    
    }    
    return ret;    
}    
int zuichu[55][55];
int gongsi[55];
int main()
{
	int n,m;
	while(~scanf("%d%d",&n,&m))
	{
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				scanf("%d",&zuichu[i][j]);
		for(int i=1;i<=n;i++)
	 		scanf("%d",&gongsi[i]);
		prepare(n+m+2,0,n+m+1);
		for(int i=1;i<=n;i++)addedge(0,i,1,0);
		for(int i=1;i<=m;i++)addedge(i+n,n+m+1,1,0);
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
			{
				if(gongsi[i]==j)addedge(i,j+n,1,-100*zuichu[i][j]-1);
				else addedge(i,j+n,1,-100*zuichu[i][j]);
			}
		int cost=SpfaFlow();
		int all=0;
	    for(int i=1;i<=n;i++)
	        all+=zuichu[i][gongsi[i]];
	    int change=n+cost%100;
	    int yaoyi=-cost/100-all;
	    printf("%d %d\n",change,yaoyi);
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值