P3236 [HNOI2014]画框

最小乘积生成树类似。

考虑维护左下凸壳上的点,每次重设边权然后求最小匹配。

具体做法和上面的链接类似,只不过最小生成树部分变成了最小匹配。

代码使用费用流:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;

#define ll long long

const int N = 155 * 155, M = 1005;

int e[N], h[N], ne[N], lim[N], cost[N], idx;

int a[M][M], b[M][M];

int n, t, S, T;

struct Point
{
	int x, y;
	Point(int _x, int _y) : x(_x), y(_y) {}
	Point()
	{
		x = y = 0;
	}
	Point operator-(const Point& xx) const
	{
		return Point(x - xx.x, y - xx.y);
	}
	int operator*(const Point& xx) const
	{
		return x * xx.y - y * xx.x;
	}
};

Point res = Point((int)1e9, (int)1e9);

void add(int u, int v, int w, int c)
{
	e[idx] = v, cost[idx] = c, lim[idx] = w, ne[idx] = h[u], h[u] = idx++;
	e[idx] = u, cost[idx] = -c, lim[idx] = 0, ne[idx] = h[v], h[v] = idx++;
}

int cur[N], dist[N];
bool isin[N];

int spfa()
{
	for (int i = 0; i <= T; i++) dist[i] = (int)1e9, isin[i] = 0;
	isin[S] = 1;
	cur[S] = h[S];
	queue<int> q;
	q.push(S);
	dist[S] = 0;
	while (q.size())
	{
		int u = q.front();
		q.pop();
		isin[u] = 0;
		for (int i = h[u]; ~i; i = ne[i])
		{
			int j = e[i];
			//printf("%lld %lld\n", u, dist[j]);
			if (lim[i] > 0 && dist[j] > dist[u] + cost[i])
			{
				dist[j] = dist[u] + cost[i];
				if (!isin[j])
				{
					isin[j] = 1;
					q.push(j);
				}
				cur[j] = h[j];
			}
		}
	}
	//system("pause");
	return dist[T] != (int)1e9;
}

inline int dfs(int u, int lm)
{
	if (u == T) return lm;
	isin[u] = 1;
	int flow = 0;
	for (int i = cur[u]; ~i && flow < lm; i = ne[i])
	{
		cur[u] = i;
		int j = e[i];
		if (lim[i] > 0 && !isin[j] && dist[j] == dist[u] + cost[i])
		{
			//printf("%d\n", j);
			int k = dfs(j, min(lim[i], lm - flow));
			flow += k;
			lim[i] -= k;
			lim[i ^ 1] += k;
		}
	}
	isin[u] = 0;
	return flow;
}

int IDX[M][M];

inline Point dinic()
{
	while (spfa())
	{
		//printf("oh\n");
		int u;
		while (u = dfs(S, (int)1e9));
	}
	Point ret = Point(0, 0);
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= n; j++)
		{
			int p = IDX[i][j + n];
			if (lim[p] == 0)
			{
				ret.x += a[i][j];
				ret.y += b[i][j];
			}
		}
	}
	if (1LL * ret.x * ret.y < 1LL * res.x * res.y)
	{
		res = ret;
	}
	return ret;
}

void solve(Point A, Point B)
{
	//printf("oh\n");
	for (int i = 0; i < N; i++) h[i] = -1, ne[i] = 0;
	idx = 0;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= n; j++)
		{
			IDX[i][j + n] = idx;
			add(i, j + n, 1, (B.x - A.x) * b[i][j] + (A.y - B.y) * a[i][j]);
		}
	}
	for (int i = 1; i <= n; i++)
	{
		add(S, i, 1, 0);
		add(i + n, T, 1, 0);
	}
	Point C = dinic();
	if ((B - A) * (C - A) >= 0) return;
	solve(A, C);
	solve(C, B);
}

signed main()
{
	scanf("%lld", &t);
	while (t--)
	{
		res = Point((int)1e9, (int)1e9);
		scanf("%d", &n);
		S = 0, T = n + n + 1;
		for (int i = 1; i <= n; i++)
		{
			for (int j = 1; j <= n; j++) scanf("%d", &a[i][j]);
		}
		for (int i = 1; i <= n; i++)
		{
			for (int j = 1; j <= n; j++) scanf("%d", &b[i][j]);
		}
		Point A, B;
		for (int i = 0; i < N; i++) h[i] = -1, ne[i] = 0;
		idx = 0;
		for (int i = 1; i <= n; i++)
		{
			for (int j = 1; j <= n; j++)
			{
				IDX[i][j + n] = idx;
				add(i, j + n, 1, a[i][j]);
			}
		}
		for (int i = 1; i <= n; i++)
		{
			add(S, i, 1, 0);
			add(i + n, T, 1, 0);
		}
		A = dinic();
		for (int i = 0; i < N; i++) h[i] = -1, ne[i] = 0;
		idx = 0;
		for (int i = 1; i <= n; i++)
		{
			for (int j = 1; j <= n; j++)
			{
				IDX[i][j + n] = idx;
				add(i, j + n, 1, b[i][j]);
			}
		}
		for (int i = 1; i <= n; i++)
		{
			add(S, i, 1, 0);
			add(i + n, T, 1, 0);
		}
		B = dinic();
		solve(A, B);
		printf("%lld\n", 1LL * res.x * res.y);
	}
	return 0;
}
posted @   HappyBobb  阅读(2)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示