HDU 3488 HDU3435 HDU 1853 (最小费用流或者最大完美匹配)

这三道题都基本差不多:给定一个图,让每一个点都在一个环中,并且使得这些环的权值之和最小。

这类型的题目有至少两种做法:最小费用流或者最大完美匹配。但是具体选择哪一种要结合具体的数据量范围。最大完美匹配的时间复杂度是O(n^3),最小费用流的时间复杂度是O(F*E*log*V),F是做最短路的次数。

最小费用流的做法:建图方式是,将一个点拆成两个,编号为i,i+n,对于一条边(u,v,val),添加一条边u->v+n,容量为inf,边权为val。然后,原点(0)向每一个点都建立一条边0->i,容量为1,权值为0。最后,每一个点i向汇点end建立一条边i->end,容量为1,边权为0。然后,求一次s到end的流量为n的最小费用流就行了。这样成立的原因是,在这个图中,每一个点都只会从s流入一个流量,向汇点流出一个流量,这个和一个环的情况是一样的,并且保证了最后边权之和是最小的。


最大完美匹配的做法:将一个点拆成两个之后建立一个二分图,一条(u,v,val)的边,就可以左边的u向右边的v建立一条权值为val的边。然后做一次最大完美匹配就行了。成立的原因是,如果有完美匹配,那么一个点在左边的点和在右边的点都被覆盖了,和在一个环中的情况一样。


HDU3435的代码(最小费用流)

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<set>
#include<vector>
#include<map>
#include<queue>
#include<climits>
#include<assert.h>
#include<functional>
using namespace std;
const int maxn=2005;
const int INF=INT_MAX/2;
typedef pair<int,int> P;

struct edge
{
	int to,cap,cost,rev;
	edge(int t,int c,int co,int r)
		:to(t),cap(c),cost(co),rev(r){}
	edge(){}
};

int V;//the number of points
vector<edge>G[maxn];
int h[maxn];
int dist[maxn];
int prevv[maxn],preve[maxn];
void add_edge(int from,int to,int cap,int cost)
{
	G[from].push_back(edge(to,cap,cost,G[to].size()));
	G[to].push_back(edge(from,0,-cost,G[from].size()-1));
}

void clear()
{
	for(int i=0;i<V;i++) G[i].clear();
}

int min_cost_flow(int s,int t,int f)
{
	int res=0;
	fill(h,h+V,0);//如果下标从1开始,就要+1
	while(f>0)
	{
		priority_queue<P,vector<P>,greater<P> >que;
		fill(dist,dist+V,INF);
		dist[s]=0;
		que.push(P(0,s));
		while(!que.empty())
		{
			P cur=que.top();que.pop();
			int v=cur.second;
			if(dist[v]<cur.first) continue;
			for(int i=0;i<G[v].size();i++)
			{
				edge &e=G[v][i];
				if(e.cap>0&&dist[e.to]>dist[v]+e.cost+h[v]-h[e.to])
				{
					dist[e.to]=dist[v]+e.cost+h[v]-h[e.to];
					prevv[e.to]=v;
					preve[e.to]=i;
					que.push(P(dist[e.to],e.to));
				}
			}
		}
		if(dist[t]==INF)
		{
			return -1;
		}
		for(int v=0;v<V;v++) h[v]+=dist[v];//从0还是1开始需要结合题目下标从什么开始

		int d=f;
		for(int v=t;v!=s;v=prevv[v])
		{
			d=min(d,G[prevv[v]][preve[v]].cap);
		}
		f-=d;
		res+=d*h[t];
		for(int v=t;v!=s;v=prevv[v])
		{
			edge &e=G[prevv[v]][preve[v]];
			e.cap-=d;
			G[v][e.rev].cap+=d;
		}
	}
	return res;
}


int main()
{
   int T;
   scanf("%d",&T);
   for(int Case=1;Case<=T;Case++)
   {
	   int n,m;
	   scanf("%d%d",&n,&m);
	   clear();
	   V=2*n+2;
	   int a,b,_v;
	   for(int i=1;i<=m;i++)
	   {
		   scanf("%d%d%d",&a,&b,&_v);
		   add_edge(a,b+n,INF,_v);
		   add_edge(b,a+n,INF,_v);
	   }
	   for(int i=1;i<=n;i++)
	   {
		   add_edge(0,i,1,0);
		   add_edge(i+n,2*n+1,1,0);
	   }
	   int mincost=min_cost_flow(0,2*n+1,n);
	   printf("Case %d: ",Case);
	   if(mincost==-1) printf("NO\n");
	   else printf("%d\n",mincost);
   }
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值