洛谷 P3231 [HNOI2013]消毒 题解

一、题目:

洛谷原题

二、思路:

这道题作为一道省选题出的非常优秀。反正我是不会做。

首先本题是对二维模型的扩展。

想一下二维的情况该怎么做。我们把行号、列号看做两排点,如果一个点\((x,y)\)需要消毒,就连从\(x\)\(y\)连一条边。

那么这张图显然是一张二分图。

再考虑一下题目的性质。我们会发现单行、单列的消毒一定不会比用几个矩形消毒更劣。

所以二维的情况就转化成用最少的行、列将需要被消毒的点覆盖。这是一个二分图的最小点覆盖模型。直接在我们刚才建好的图上跑二分图匹配即可。


再来考虑三维。突然感觉难度上升了一个台阶,直接做并不好做。

再来挖掘题目性质。注意到\(abc\leq 5000\),设\(z = \min\{a,b,c\}\),则\(z\leq\sqrt[3]{5000}<18\)

所以我们把最小的那维拿出来当做长方体的高。爆搜出哪些层不选,哪些层选。

对于每个不选的层,我们直接用一个高为1的长方体覆盖即可。

对于选择的层,我们把这些层拍扁到一层,用二维的方法去做。对应到原来的三维情况就是,我们用一些长为1或宽为1的扁长方体把点覆盖掉。这样的话是最优的。

三、细节

本题比较玄学。首先需要注意数组的大小。

我因为数组开过大,导致程序不能运行。我还以为是我家电脑坏了,就重装了一下电脑。结果换了台电脑,发现程序还是不能运行,这才发现是我程序的问题!

然后注意数组尽量不要memset,常数会很大。

最后就是Dinic好像还没有匈牙利快。我搞不懂是为什么。

四、代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <ctime>

using namespace std;
#define LL long long
#define FILEIN(s) freopen(s".in", "r", stdin)
#define FILEOUT(s) freopen(s".out", "w", stdout)
#define mem(s, v) memset(s, v, sizeof s)

#define x first
#define y second

inline LL read(void) {
	LL x = 0, f = 1; char ch = getchar();
	while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
	while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
	return f * x;
}

const int maxn = 5e3 + 5, inf = 1e8;

int X, Y, Z, head[maxn << 1], tot;

int map[maxn][72][18];
int tmp[maxn][72], match[maxn << 1];
bool vis[maxn << 1];

struct Edge {
	int y, next;
	Edge() {}
	Edge(int _y, int _next) : y(_y), next(_next) {}
}e[maxn];

inline void add(int x, int y) {
	e[++ tot] = Edge(y, head[x]);
	head[x] = tot;
}

inline void init(void) {
	pair<int, int> a[4];
	int p[4];
	
	for (int i = 1; i <= 3; ++ i) a[i].x = read(), a[i].y = i;
	if (a[1].x < a[2].x) swap(a[1], a[2]);
	if (a[1].x < a[3].x) swap(a[1], a[3]);
	if (a[2].x < a[3].x) swap(a[2], a[3]);

	for (int i = 1; i <= 3; ++ i) p[a[i].y] = i;

	X = a[1].x; Y = a[2].x; Z = a[3].x;
	for (int i = 1; i <= a[p[1]].x; ++ i)
		for (int j = 1; j <= a[p[2]].x; ++ j)
			for (int k = 1; k <= a[p[3]].x; ++ k) {
				if (p[1] == 1) {
					if (p[2] == 2) map[i][j][k] = read();
					else map[i][k][j] = read();
				} else if (p[1] == 2) {
					if (p[2] == 1) map[j][i][k] = read();
					else map[k][i][j] = read();
				} else {
					if (p[2] == 1) map[j][k][i] = read();
					else map[k][j][i] = read();
				}
			}
}

bool dfs(int x) {
	for (int i = head[x]; i; i = e[i].next) {
		int y = e[i].y;
		if (!vis[y]) {
			vis[y] = true;
			if (!match[y] || dfs(match[y])) {
				match[y] = x; return true;
			} 
		}
	}
	return false;
}

int main() {
	int Q = read();
	while (Q --) {
		init();
		
		int ans = 0x3f3f3f3f;
		for (int s = 0; s < (1 << Z); ++ s) {
			int sum = 0;
			
			for (int i = 1; i <= X; ++ i) head[i] = 0;
			for (int i = 1; i <= Y; ++ i) match[i + X] = 0;
			tot = 0;
			
			for (int i = 1; i <= X; ++ i) 
				for (int j = 1; j <= Y; ++ j) 
					tmp[i][j] = 0;
					
			for (int k = 1; k <= Z; ++ k) {
				if (!((s >> (k - 1)) & 1)) ++ sum;
				else {
					for (int i = 1; i <= X; ++ i) 
						for (int j = 1; j <= Y; ++ j)
							tmp[i][j] |= map[i][j][k];
				}
			}
			if (sum > ans) continue;
			for (int i = 1; i <= X; ++ i) 
				for (int j = 1; j <= Y; ++ j) 
					if (tmp[i][j]) add(i, j + X);  
						
			for (int i = 1; i <= X; ++ i) {
				for (int j = 1; j <= Y; ++ j) 
					vis[j + X] = false;
				if (dfs(i)) ++ sum;
			}
			ans = min(ans, sum);
		}
		printf("%d\n", ans);
	}
	return 0;
}
posted @ 2021-02-17 12:00  蓝田日暖玉生烟  阅读(75)  评论(0编辑  收藏  举报