洛谷P2045 方格取数加强版

本文介绍了一种利用网络流中的费用流算法解决特定问题的建模方法,通过拆点策略确保每个数仅被选取一次。文章详细阐述了如何设置节点与边的流量及费用,以实现最优路径的选择,避免重复计数,并附带完整代码示例。

题目:Link

网络流 - 费用流

一道比较好建模的题,重点是怎样让每个数只被取一次
控制每个点被走的次数,我们可以很容易想到拆点
对于每个点,我们在入点和出点之间连两条边,一条流量为 1 1 1,费用为该格子的权值;另一条流量为 ∞ \infty ,费用为 0 0 0。除此之外,每个点还要分别向右边和下面的连边,流量为 ∞ \infty ,费用为 0 0 0

最后,超级源点 s s s 向点 ( 1 , 1 ) (1,1) (1,1) 连边,且 ( n , n ) (n,n) (n,n) 向超级汇点 t t t 连边,流量为 k k k,费用为 0 0 0

你也许会想到(像我一样):如果在跑费用流时,先跑了 入点和出点之间的,流量为 ∞ \infty 的边(也就是第一次到达方格时没有取数)
然而,这种情况是不可能的。因为最大费用流最大流是以费用为边权求最长路,所以一定会挑费用高的走,就不可能出现以上情况了

代码:

#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
const int Maxn=5000+20,Maxm=30000+10,inf=0x3f3f3f3f;
int nxt[Maxm],to[Maxm],flow[Maxm],cost[Maxm];
int head[Maxn],dis[Maxn],pre[Maxn],cur[Maxn];
bool vis[Maxn];
int n,m,k,s,t,ans;
int edgecnt=1;
inline int read()
{
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0' && ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
	return s*w;
}
inline void add(int x,int y,int v,int c)
{
	++edgecnt;
	nxt[edgecnt]=head[x];
	to[edgecnt]=y;
	flow[edgecnt]=v;
	cost[edgecnt]=c;
	head[x]=edgecnt;
}
inline int id(int x,int y)
{
	return (x-1)*m+y;
}
bool spfa()
{
	fill(dis+1,dis+4+(n<<1),-inf);
	fill(cur+1,cur+4+(n<<1),inf);
	queue <int> q;
	vis[s]=1,dis[s]=0,q.push(s);
	while(q.size())
	{
		int x=q.front();
		q.pop();
		vis[x]=0;
		for(int i=head[x];i;i=nxt[i])
		{
			int y=to[i];
			if(!flow[i])continue;
			if(dis[y]<dis[x]+cost[i])
			{
				dis[y]=dis[x]+cost[i];
				pre[y]=i,cur[y]=min(cur[x],flow[i]);
				if(!vis[y])vis[y]=1,q.push(y);
			}
		}
	}
	if(dis[t]==-inf)return 0;
	return 1;
}
void upd()
{
	int x=t;
	while(x!=s && x)
	{
		int i=pre[x];
		flow[i]-=cur[t];
		flow[i^1]+=cur[t];
		x=to[i^1];
	}
	ans+=cur[t]*dis[t];
}
int main()
{
//	freopen("in.txt","r",stdin);
	m=read(),k=read();
	n=m*m,s=(n<<1|1) ,t=s+1;
	add(s,id(1,1),k,0);
	add(id(1,1),s,0,0);
	add(id(m,m)+n,t,k,0);
	add(t,id(m,m)+n,0,0);
	for(int i=1;i<=m;++i)
	{
		for(int j=1;j<=m;++j)
		{
			int tmp=read();
			int x=id(i,j);
			add(x,x+n,1,tmp);
			add(x+n,x,0,-tmp);
			add(x,x+n,inf,0);
			add(x+n,x,0,0);
			if(i!=m)
			{
				add(x+n,id(i+1,j),inf,0);
				add(id(i+1,j),x+n,0,0);
			}
			if(j!=m)
			{
				add(x+n,id(i,j+1),inf,0);
				add(id(i,j+1),x+n,0,0);
			}
		}
	}
	
	while(spfa())upd();
	printf("%d\n",ans);
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值