JZOJ 3447.摘取作物

\(\text{Problem}\)

在一个矩阵里选数,每行最多选两个,每列最多选两个,最大会价值
\(n,m \le 30\)

\(\text{Analysis}\)

对个这个限制如何实现?
跑费用流
把行看做点,列看做点
点对 \((i,j)\) 就用 \(i\) 行点连向 \(j\) 列点,流量为 \(1\),费用为 \(-v[i][j]\)
原点向行点连一条流量为 \(2\),费用为 \(0\) 的边,列点向汇点连一条流量为 \(2\),费用为 \(0\) 的边
这样就可以保证限制了
因为跑 \(spfa\) 得到最小费用,但要注意不一定要最大流,可行流即可
所以 \(spfa\) 后判断 \(dis[T]\) 的正负情况,决定是否继续

\(\text{Code}\)

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;

const int N = 905;
int n , m , S = 0 , T, Maxflow , Mincost;
int dis[N] , h[N] , vis[N] , pre[N] , edge[N] , flow[N] , tot = 1;

queue<int> d;
struct node{
	int to , nxt , w , f;
}e[N << 1 + 10];

inline void add(int u , int v , int w , int f){e[++tot] = node{v, h[u], w, f}, h[u] = tot;}

int spfa()
{
	memset(vis , 0 , sizeof(vis));
	memset(dis , 127 , sizeof(dis));
	memset(flow , 127 , sizeof(flow));
	d.push(S);
	vis[S] = 1 , dis[S] = 0 , pre[T] = -1;
	while (!d.empty())
	{
		int now = d.front();
		d.pop(), vis[now] = 0;
		for(register int i = h[now]; i; i = e[i].nxt)
		if (dis[e[i].to] > dis[now] + e[i].f && e[i].w)
		{
			dis[e[i].to] = dis[now] + e[i].f;
			flow[e[i].to] = min(flow[now] , e[i].w);
			pre[e[i].to] = now, edge[e[i].to] = i;
			if (!vis[e[i].to]) vis[e[i].to] = 1 , d.push(e[i].to);
		} 
	}
	return (dis[T] < 0 && pre[T] != -1);
}

int MCMF()
{
	while (spfa()) 
	{
		Maxflow += flow[T], Mincost += dis[T] * flow[T];
		int now = T;
		while (now != S)
			e[edge[now]].w -= flow[T], e[edge[now] ^ 1].w += flow[T], now = pre[now];
	}
	return Mincost;
}

int main()
{
	freopen("pick.in", "r", stdin);
	freopen("pick.out", "w", stdout);
	scanf("%d%d", &n, &m), T = n + m + 1;
	for(int i = 1, x; i <= n; i++)
		for(int j = 1; j <= m; j++) scanf("%d", &x), add(i, j + n, 1, -x), add(j + n, i, 0, x);
	for(int i = 1; i <= n; i++) add(S, i, 2, 0), add(i, S, 0, 0);
	for(int j = 1; j <= m; j++) add(j + n, T, 2, 0), add(T, j + n, 0, 0);
	printf("%d\n", -MCMF());
}
posted @ 2021-07-14 15:23  leiyuanze  阅读(32)  评论(0编辑  收藏  举报