最近在生物实验室工作的小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中的最小 者。)
仅包含D行,每行一个整数,表示对应实验皿最少要用多少单位的F试剂。
首先由均值不等式可以得到,min{a, b, c} <= 17
,不妨假设最短的那条边为a
。接下来考虑消除,答案肯定不会超过a
,因为可以做若干次1*b*c
的消除。
一开始看到这个结论我还是非常傻逼的以为,只要把需要消毒的每一片1*b*c
都消毒即可,还傻傻问副队哪来的二分图……经过副队的教导才意识到这样不一定最优,比较科学的做法是:2^a
枚举每层是否消掉,然后用a*1*c
和a*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; }