FZOJ 4112 脱单计划

这是一道费用流的好题。

首先题目给你的男士女士以及之间的关系可以看做一个二分图。考虑暴力连边(用\([x,y]\)表示流量为\(x\),费用为\(y\)的边):

  • 源点像每个男士小区连\([k,0]\)(\(k\)表示小区的人数)
  • 每个男士小区向每个女士小区连\([INF,dis]\)\(INF\)为无穷大,\(dis\)为两个小区之间的曼哈顿距离)
  • 每个女士小区向汇点连\([k,0]\)

然后时间复杂度是\(O(nmf)\),不能通过此题。

考虑暴力连边边的数量是\(O(n^2)\)级别的,是否能减少连边的数量。

现在假设每个男士小区和女士小区的距离为\(x1+y1-x2-y2\)(也就是去掉了绝对值的曼哈顿距离,其中\(x1,y1\)分别表示男士小区的横、纵坐标,\(x2,y2\)分别表示女士小区的横、纵坐标),那么我们可以建一个辅助点\(P\),男士小区向\(P\)连费用为\(x1+y1\)的边,\(P\)向女士小区连费用为\(-x1-y1\)的边,由于费用会累加的缘故,我们发现这样连边的效果和暴力是相同的,而边的数量却是\(O(n)\)级别的。

那么加上绝对值该怎么做呢?我们可以分类讨论\(|x1-x2|+|y1-y2|\)的四种情况,建四个辅助点每个都向类似上文连边,发现如果某种连边方式如果与\(|x1-x2|+|y1-y2|\)的值并不相等也只能小于它,而我们求的是最大费用流,所以这些“错误”的边一定会被“正确”的边取代,从而保证答案的正确。

注:由于最后要求最大费用流,所以实际连边的时候费用应全部取相反数。

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<deque>

using namespace std;

typedef long long LL;
const int N=5000;
const LL INF=1LL<<60;
deque <int> q;
int n,Male[N],Female[N],xM[N],yM[N],xF[N],yF[N],kM[N],kF[N],S,T;
int tot,head[N*10],cnt=1,inq[N],vis[N],cur[N],P[10];
LL dis[N],Mincost;
struct Edge
{
	int nxt,to;
	LL c,w;
}g[N*10];

void add(int from,int to,LL w,LL c)
{
	g[++cnt].nxt=head[from];
	g[cnt].to=to;
	g[cnt].w=w;
	g[cnt].c=c;
	head[from]=cnt;
}

void ADD(int from,int to,LL w,LL c)
{
	add(from,to,w,c),add(to,from,0,-c);
}

void init()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
		scanf("%d %d %d",&xM[i],&yM[i],&kM[i]),Male[i]=++tot;
	for (int i=1;i<=n;i++)
		scanf("%d %d %d",&xF[i],&yF[i],&kF[i]),Female[i]=++tot;
}

int Dis1(int i,int type)
{
	return type==1?xM[i]+yM[i]:xF[i]+yF[i];
}

int Dis2(int i,int type)
{
	return type==1?xM[i]-yM[i]:xF[i]-yF[i];
}

int Dis3(int i,int type)
{
	return type==1?-xM[i]+yM[i]:-xF[i]+yF[i];
}

int Dis4(int i,int type)
{
	return type==1?-xM[i]-yM[i]:-xF[i]-yF[i];
}

void clear()
{
	memset(vis,0,sizeof(vis));
	memcpy(cur,head,sizeof(cur));
	for (int i=1;i<=tot;i++)
		dis[i]=INF;
}

int SPFA()
{
	clear(),q.push_back(S);
	while(!q.empty())
	{
		int x=q.front();q.pop_front();
		inq[x]=0;
		for (int i=head[x];i;i=g[i].nxt)
		{
			int v=g[i].to;
			if(dis[v]<=dis[x]+g[i].c||g[i].w<=0)
				continue;
			dis[v]=dis[x]+g[i].c;
			if(!inq[v])
			{
				inq[v]=1;
				if(!q.empty()&&dis[q.front()]>=dis[v])
					q.push_front(v);
				else
					q.push_back(v);
			}
		}
	}
	return dis[T]!=INF;
}

LL dfs(int x,LL Min)
{
	if(x==T)
		return Min;
	LL flow=0;
	vis[x]=1;
	for (int &i=cur[x];i;i=g[i].nxt)
	{
		int v=g[i].to;
		if(dis[v]!=dis[x]+g[i].c||g[i].w<=0||vis[v])
			continue;
		LL tmp=dfs(v,min(Min-flow,g[i].w));
		flow+=tmp,g[i].w-=tmp,g[i^1].w+=tmp,Mincost+=tmp*g[i].c;
		if(flow==Min)
			break;
	}
	return flow;
}

int Dinic()
{
	int ans=0;
	while(SPFA())
		ans+=dfs(S,INF);
	return ans;
}

void work()
{
	T=++tot;
	for (int i=1;i<=4;i++)
		P[i]=++tot;
	for (int i=1;i<=n;i++)
	{
		ADD(S,Male[i],kM[i],0),ADD(Female[i],T,kF[i],0);
		ADD(Male[i],P[1],INF,-Dis1(i,1)),ADD(P[1],Female[i],INF,-Dis4(i,0));
		ADD(Male[i],P[2],INF,-Dis2(i,1)),ADD(P[2],Female[i],INF,-Dis3(i,0));
		ADD(Male[i],P[3],INF,-Dis3(i,1)),ADD(P[3],Female[i],INF,-Dis2(i,0));
		ADD(Male[i],P[4],INF,-Dis4(i,1)),ADD(P[4],Female[i],INF,-Dis1(i,0));
	}
	Dinic();
	printf("%lld\n",-Mincost);
}

int main()
{
	init();
	work();
	return 0;
}
posted @ 2020-04-17 15:31  With_penguin  阅读(120)  评论(0编辑  收藏  举报