[HNOI2013]消毒(三维二分图匹配)

[HNOI2013]消毒(luogu)

题目描述

 

最近在生物实验室工作的小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,表示数据组数。接下来是D组数据,每组数据开头是三个数a,b,c表示实验皿的尺寸。

接下来会出现a个b 行c列的用空格隔开的01矩阵,0表示对应的单位立方体不要求消毒,1表示对应的单位立方体需要消毒;

例如,如果第1个01矩阵的第2行第3列为1,则表示单位立方体(1,2,3)需要被消毒。

输入保证满足a*b*c<=5000,T<=3。

输出格式

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

Solution

有一个较为明显的贪心策略:每次消毒令x,y,z中一个为 1 ,其他为最大值

三分图匹配不会(可能是我太蒻了),发现a,b,c中一定有一个 <= 50001/3 ~=17.1,217复杂度还可以接受。

于是可以旋转长方体,使 a,b,c 中最小的一个为长方形的高,不妨设它为a。

设同一高度的所有单位立方体为一层,枚举每一层是对这一层全部消毒还是留下

留下来的压缩到一个平面上,然后就是二维的问题了。

题外话:

除二分图匹配的 “ 0 要素 ” 和 “ 1 要素 ”外,最小点覆盖还有一个 “ 2 要素”:

每条边有 2 个端点,二者至少选择一个

将平面上每个需要消毒的单位正方形的所在行数和列数连边,求最小点覆盖

Code

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=5e3+10;
int match[N],head[N],nxt[N],tot,ver[N];
int T,a,b,c,n,ans,pos,s[3][N],cnt,vis[N];
bool flag[N];
void add(int x,int y)
{
    ver[++tot]=y,nxt[tot]=head[x],head[x]=tot;
}
bool dfs(int x)
{
    for(int i=head[x],y;i;i=nxt[i])
        if(vis[y=ver[i]]!=cnt)
        {
            vis[y]=cnt;
            if(!match[y] || dfs(match[y]))
            {
                match[y]=x;
                return 1;
            }
        }
    return 0;
}
void clear()
{
    tot=0;
    memset(head,0,sizeof(head));
    memset(match,0,sizeof(match));
}
int solve(int x)
{
    clear();
    int now=0;
    for(int i=0;i<a;i++)
        if(x&(1<<i)) flag[i+1]=false,now++;
        else flag[i+1]=true;
    for(int i=1;i<=n;i++)
        if(flag[s[0][i]]) add(s[1][i],s[2][i]);
    for(int i=1;i<=b;i++)
    {
        cnt++;
        if(dfs(i)) now++;
    }
    return now;
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        n=cnt=0,ans=1<<30;
        memset(vis,0,sizeof(vis));
        scanf("%d%d%d",&a,&b,&c);
        for(int i=1;i<=a;i++)
            for(int j=1;j<=b;j++)
                for(int k=1;k<=c;k++)
                {
                    scanf("%d",&pos);
                    if(!pos) continue;
                    s[0][++n]=i,s[1][n]=j,s[2][n]=k;
                }
        int mi=min(a,min(c,b));
        if(mi==b) swap(a,b),swap(s[0],s[1]);
        if(mi==c) swap(a,c),swap(s[0],s[2]);
        for(int i=0;i<(1<<a);i++)
            ans=min(ans,solve(i));
        printf("%d\n",ans);
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/hsez-cyx/p/12348658.html