洛谷P2045&&POJ3422 K取方格数题解

3 篇文章 0 订阅
2 篇文章 0 订阅

版权声明:以下大部分内容摘自《算法竞赛进阶指南》,李煜东著,河南电子音像出版社。

题目描述

给出一个n*n的矩阵,每一格有一个非负整数Aij,(Aij <= 1000)现在从(1,1)出发,可以往右或者往下走,最后到达(n,n),每达到一格,把该格子的数取出来,该格子的数就变成0,这样一共走K次,现在要求K次所达到的方格的数的和最大
输入格式
第一行两个数n,k(1<=n<=50, 0<=k<=10)
接下来n行,每行n个数,分别表示矩阵的每个格子的数
输出格式
一个数,为最大和
输入输出样例
输入
3 1
1 2 3
0 2 1
1 4 2
输出
11
说明/提示
每个格子中的数不超过1000

考虑怎么做?
发现这道题的难点是数最多只能取一次
K=1时直接求(1,1)到(n,n)的最长路
K=2时是经典的四维DP,是DP入门题
然而K=10?(N维DP?你可以打打,将AC程序发给我)
发现格子里的数最多被取一次,这是上界。
而答案要求最大,这是权值最大。
考虑最大费用最大流的基本思路,那么问题就是!!建图!
首先,将一个点拆成两个点,入点和出点。
其次,对于点(i,j)的入点和出点,连两条有向边。
第一条:容量为1,费用为格子(i,j)里的数。
保证了当走第一条边时,走完一次后,就不会再走。
为什么容量是1?注意,W是单位费用,这样一方面保证了最多取一次费用,也能保证取到的费用一定为W。
第二条:容量为K-1,费用为0.
虽然费用只能取一次,但点(i,j)可以经过K次。
第二条边保证了这个点可以经过K次,且只取一次费用(第二条边费用为0)
最后,从点(i,j)的出点到点(i,j+1)和点(i+1,j)的入点连一条边,容量为K,费用为0.
可以往右(下)走;同时限制流量。
以点(1,1)入点为源点,以点(N,N)出点为汇点,因为点(1,1)入点与出点两条有向边只有K的容量,且“最大费用最大流”先保证流量最大,再保证权值最大,所以恰好会找到K条路线,本题答案即为最后的总答案。
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
int f[200005][5],q[10005];
int dist[10005],flow[10005],spfa[10005],pre[10005],last[10005];
int i,j,k,m,n,o,p,l,s,t,S,T,times,c,ans;
void insert(int x,int y,int z,int w) {
	f[++t][1]=y,f[t][2]=q[x],f[t][3]=z,f[t][4]=w,q[x]=t;
	f[++t][1]=x,f[t][2]=q[y],f[t][3]=0,f[t][4]=-w,q[y]=t;
}
int num(int x,int y,int z) {return x*n*n+(y-1)*n+z;}
int costflow()
{
	queue<int>h;
	memset(dist,-1,sizeof(dist));memset(spfa,0,sizeof(spfa));
	h.push(S);flow[S]=1e9;dist[S]=0;spfa[S]=1;
	while (!h.empty())
	{
		int st=h.front();h.pop();
		for (int k=q[st];k;k=f[k][2])
		{
			if (!f[k][3]) continue;
			if (dist[st]+f[k][4]>dist[f[k][1]])
			{
				dist[f[k][1]]=dist[st]+f[k][4];
				flow[f[k][1]]=min(flow[st],f[k][3]);pre[f[k][1]]=k;
				if (!spfa[f[k][1]]) h.push(f[k][1]),spfa[f[k][1]]=1;	
			}	
		}spfa[st]=0;
	}
	return dist[T]!=-1;
}
void update()
{
	ans+=dist[T]*flow[T];
	int k=T;
	while (k!=S)
	{
		f[pre[k]][3]-=flow[T];
		f[pre[k]^1][3]+=flow[T];
		k=f[pre[k]^1][1];
	}
}
int read(int &x)
{
	char ch=getchar();x=0;
	while (ch<'0'||ch>'9') ch=getchar();
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-48,ch=getchar();
}
int main()
{
	read(n),read(times);t=1;
	for (i=1;i<=n;i++)
		for (j=1;j<=n;j++)
		{
			read(c);
			insert(num(0,i,j),num(1,i,j),1,c);
			insert(num(0,i,j),num(1,i,j),times-1,0);
			if (i<n) insert(num(1,i,j),num(0,i+1,j),times,0);
			if (j<n) insert(num(1,i,j),num(0,i,j+1),times,0); 
		}
	S=1,T=2*n*n;
	while (costflow()) 
		update();
	printf("%d\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值