[Hnoi2013]消毒

[Hnoi2013]消毒

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 376  Solved: 158
[ Submit][ Status]

Description

最近在生物实验室工作的小T遇到了大麻烦。
由于实验室最近升级的缘故,他的分格实验皿是一个长方体,其尺寸为a*b*c,a、b、c 均为正整数。为了实验的方便,它被划分为a*b*c个单位立方体区域,每个单位立方体尺寸
为1*1*1。用(i,j,k)标识一个单位立方体,1 ≤i≤a,1≤j≤b,1≤k≤c。这个实验皿已经很久没有人用了,现在,小T被导师要求将其中一些单位立方体区域进 行消毒操作(每个区域可以被重复消毒)。而由于严格的实验要求,他被要求使用一种特定 的F试剂来进行消毒。 这种F试剂特别奇怪,每次对尺寸为x*y*z的长方体区域(它由x*y*z个单位立方体组 成)进行消毒时,只需要使用min{x,y,z}单位的F试剂。F试剂的价格不菲,这可难倒了小 T。现在请你告诉他,最少要用多少单位的F试剂。(注:min{x,y,z}表示x、y、z中的最小 者。)

Input

第一行是一个正整数D,表示数据组数。接下来是D组数据,每组数据开头是三个数a,b,c表示实验皿的尺寸。接下来会出现a个b 行c列的用空格隔开的01矩阵,0表示对应的单位立方体不要求消毒,1表示对应的单位立方体需要消毒;例如,如果第1个01矩阵的第2行第3列为1,则表示单位立方体(1,2,3)需要被消毒。输入保证满足a*b*c≤5000,T≤3。

Output

仅包含D行,每行一个整数,表示对应实验皿最少要用多少单位的F试剂。

Sample Input

1
4 4 4
1 0 1 1
0 0 1 1
0 0 0 0
0 0 0 0
0 0 1 1
1 0 1 1
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
1 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
1 0 0 0


题解转的!!

首先由均值不等式可以得到,min{a, b, c} <= 17,不妨假设最短的那条边为a。接下来考虑消除,答案肯定不会超过a,因为可以做若干次1*b*c的消除。

一开始看到这个结论我还是非常傻逼的以为,只要把需要消毒的每一片1*b*c都消毒即可,还傻傻问副队哪来的二分图……经过副队的教导才意识到这样不一定最优,比较科学的做法是:2^a枚举每层是否消掉,然后用a*1*ca*b*1的片覆盖,也就是转成二维的最小棋盘覆盖。

所谓最小棋盘覆盖就是说,每次可以覆盖掉棋盘的一整行或一整列,问把所有标记点覆盖的最小次数。嗯,这个也不会做……Google了一下,看到搜索结果里面最小点覆盖行列拆开这两个关键词才突然意识到,这不就是个水水的二分图匹配嘛。

考虑把所有行放在左边,所有列放在右边,然后如果(i, j)需要被覆盖,那么连边(Xi, Yj)。之后就是一个最小点覆盖的问题了(选择最少的点,使得每条边都至少有一个端点被选择),因为最小点覆盖=最大匹配,所以轻松做掉了。(这个时候突然很庆幸前不久刚学匈牙利,明显这种时候用匈牙利跑匹配比网络流省事多了=v=)

常数

虽然写程序的时候已经在清数组的时候注意了,但是手测第一个点还是TLE得非常严重(吐槽一下,所有数据都是10K……)。后来看到副队博客上还有一句话,发现有一个非常卡BUG的优化方法:因为1的数量非常少,所以可以把1用链表串起来。嗯,加了优化后就可以过了。不知道不卡BUG是如何做的= =?


#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn = 5000;
const int maxm = 30;
int a, b, c;
class Dread{
    private:
        bool isdigit(char ch) { return ch >= '0' && ch <= '9'; }
        bool isalpha(char ch) { return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); }
        void Getchar(int &tmp){
            char ch; tmp = 0; bool b = true;
            while (ch = getchar()){
				if (ch == '-') b = false;
				if (isdigit(ch)) break;
            }
			for (; isdigit(ch); ch = getchar()) tmp = tmp * 10 + ch - '0';
        	if (!b) tmp = -tmp;
		}
        void Getchar(char &tmp){
            while (tmp = getchar()) if (isalpha(tmp)) break;
        }
    public:
        int Int(){ int x; Getchar(x); return x; }
        char Ch(){ char x; Getchar(x); return x; }
}Read;

struct Tpoint{
	int x, y, z;
	Tpoint() {}
	Tpoint(int _x, int _y, int _z) : x(_x), y(_y), z(_z) {
		if (a <= b && a <= c) swap(x, x);
		else if (b <= a && b <= c) swap(x, y);
		else if (c <= a && c <= b) swap(x, z);
	}
}point[maxn];
int pre[maxn], now[maxn], son[maxn], v[maxn];
int sum, tot;
void cc(int a, int b, int c){
	pre[++ tot] = now[a];
	now[a] = tot;
	son[tot] = b;
	v[tot] = c;
}
void init(){
	a = Read.Int(), b = Read.Int(), c = Read.Int();
	sum = 0;
	for (int i = 1; i <= a; i ++)
		for (int j = 1; j <= b; j ++)
			for (int k = 1; k <= c; k ++){
				int x = Read.Int();
				if (x) point[++ sum] = Tpoint(i, j, k);
			}
	if (a <= b && a <= c) swap(a, a);
	else if (b <= a && b <= c) swap(a, b);
	else if (c <= a && c <= b) swap(a, c);
	tot = 0; memset(now, 0, sizeof(now));
	for (int i = 1; i <= sum; i ++)
		cc(point[i].y, point[i].z, point[i].x);
}
bool used[maxm], vis[maxm];
int l[maxm];
bool find(int x){
	for (int p = now[x]; p; p = pre[p]){
		if (!used[v[p]] && !vis[son[p]]){
			vis[son[p]] = true;
			if (!l[son[p]] || find(l[son[p]])){
				l[son[p]] = x;
				return true;
			}
		}
	}
	return false;
}
int ans = 0;
void dfs(int x, int num){
	if (num >= ans) return;
	if (x > a){
		int s = 0;
		memset(l, 0, sizeof(l));
		for (int i = 1; i <= b; i ++){
			memset(vis, 0, sizeof(vis));
			s += find(i);
			if (s + num >= ans) return;
		}
		ans = num + s;
	}
	dfs(x + 1, num);
	used[x] = 1; 
	dfs(x + 1, num + 1);
	used[x] = 0;
}
void work(){
	ans = a;
	dfs(1, 0);
	cout <<ans <<endl;
}
int main(){
	int t = Read.Int();
	while (t --){
		init(); work();
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值