[网络流24题 #16]数字梯形问题

这是一道很经典的费用流题目,可以通过最大费用最大流实现
通过分析题目,我们很容易想到建模方法:

1、对于规则一,题目规定每一个节点只能访问一次,也就是说,实际上这个时候每一个点都有容量限制,所以我们必须把点i拆分成两个点Xi,Yi。此时每一条边的容量都是1,费用为改点的权值,此时只允许使用该节点一次,并且代价为权值。数字梯形是逐层向下的,并且每一个点可以向下面两个方向进行扩展,于是我们在这些点之间建立容量为1,费用为0的边。对于最顶层与最底层,我们分让两层分别与源点和汇点相连,容量同样是1,费用为0。(注意所有的边都是有向边,并且结点i与j相连是指Yi与Xj相连)

2、对于规则二,由于可以共点,所以就不用拆点了,源点与上层继续保持容量为1,其他的关联除了不用拆点外,其他都差不多,注意一下底层与汇点的容量限制改为无穷大即可。

3、对于规则三,由于边和节点都可以共用,此时没有必要把原网络销毁,直接在上一个规则的前提下修改每一条边的容量即可(注意源点与顶层节点之间的容量是不能变的,否则就无法满足是m条路径取得的最大值,其他边的容量改成无穷大即可)

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <queue>
#define add(x) Q.push(x),v[x]=1
#define del(x) x=Q.front(),Q.pop(),v[x]=0
#define MaxN 40
#define MaxP 800
using namespace std;
const int INF=~0U>>2;
int map[MaxN][MaxN],num[MaxN][MaxN];
int head[MaxP],p[MaxP][2],d[MaxP];
int tot,n,m,s,t,TotP;
bool v[MaxP];
struct edge
{
	int v,cap,cost,next;
	edge(int x,int y,int c,int b):
	v(y),cap(c),cost(b),next(head[x])
	{
		head[x]=tot++;
	}
};
vector<edge> a;
inline void AddEdge(int x,int y,int c,int b)
{
	a.push_back(edge(x,y,c,b));
	a.push_back(edge(y,x,0,-b));
}
inline void InitFlow()
{
	tot=0,a.clear();
	memset(head,-1,sizeof(head));
}
inline void init()
{
	cin>>m>>n;
	for(int i=1;i<=n;i++)
		for(int j=1;j<(i+m);j++)
			scanf("%d",&map[i][j]),
			num[i][j]=++TotP;
}
inline bool LPFA()
{
	int x,y;
	memset(v,0,sizeof(v));
	for(int i=1;i<=t;i++)
		d[i]=-INF;
	queue<int> Q;
	add(s),d[s]=0;
	while(!Q.empty())
	{
		del(x);
		for(int i=head[x];i!=-1;i=a[i].next)
		{
			y=a[i].v;
			if(a[i].cap&&d[x]+a[i].cost>d[y])
			{
				d[y]=d[x]+a[i].cost;
				p[y][0]=i,p[y][1]=x;
				if(!v[y]) add(y);
			}
		}
	}
	return d[t]!=-INF;
}
inline int MaxCost()
{
	int ans=0,flow;
	while(LPFA())
	{
		flow=INF;
		for(int i=t;i!=s;i=p[i][1])
			if(flow>a[p[i][0]].cap)
				flow=a[p[i][0]].cap;
		for(int i=t;i!=s;i=p[i][1])
			a[p[i][0]].cap-=flow,
			a[p[i][0]^1].cap+=flow;
		ans+=d[t]*flow;
	}
	return ans;
}
inline void work()
{
	/*Ques 1:*/
	InitFlow();
	t=TotP*2+1;
	for(int i=1;i<=m;i++)
		AddEdge(s,i,1,0);
	for(int i=1;i<=n;i++)
		for(int j=1;j<(i+m);j++)
			AddEdge(num[i][j],num[i][j]+TotP,1,map[i][j]);
	for(int i=1;i<n;i++)
		for(int j=1;j<(i+m);j++)
			AddEdge(num[i][j]+TotP,num[i+1][j],1,0),
			AddEdge(num[i][j]+TotP,num[i+1][j+1],1,0);
	for(int i=1;i<(n+m);i++)
		AddEdge(num[n][i]+TotP,t,1,0);
	cout<<MaxCost()<<endl;
	/*Ques 2:*/
	InitFlow();
	t=TotP+1;
	for(int i=1;i<=m;i++)
		AddEdge(s,i,1,0);
	for(int i=1;i<n;i++)
		for(int j=1;j<(m+i);j++)
			AddEdge(num[i][j],num[i+1][j],1,map[i][j]),
			AddEdge(num[i][j],num[i+1][j+1],1,map[i][j]);
	for(int i=1;i<(n+m);i++)
		AddEdge(num[n][i],t,INF,map[n][i]);
	cout<<MaxCost()<<endl;
	/*Ques 3:*/
	for(int i=0;i<2*m;i+=2)
		a[i].cap=1,
		a[i+1].cap=0;
	for(int i=2*m;i<tot;i+=2)
		a[i].cap=INF,
		a[i+1].cap=0;
	cout<<MaxCost()<<endl;
}
int main()
{
	init();
	work();
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值