bzoj3140: [Hnoi2013]消毒 二分图匹配

题目链接:戳这里

3140: [Hnoi2013]消毒

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 1365  Solved: 598
[ Submit][ Status][ Discuss]

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

Sample Output

3

HINT

对于区域(1,1,3)-(2,2,4)和(1,1,1)-(4,4,1)消毒,分别花费2个单位和1个单位的F试剂。2017.5.26新加两组数据By Leoly,未重测.

Source

先喷一波新加数据:我tm被卡常卡成狗了。

分析:此题是个立体问题。

首先可以得出结论:每次消毒肯定是1*b*c,a*1*c,a*b*1的区域,所以问题相当于用多少个面能覆盖掉空间内的所有点。

如果先抽象成一个平面问题:用多少条线能覆盖掉平面内的所有点?那么显然是行列连边做二分图最大匹配了。如果放到空间内该怎么办呢?并没有三分图匹配这种东西。

考虑到a*b*c<=5000,那么a,b,c中最小的值不会超过5000的立方根,即不会超过17(17*17*17=4913)。

那么我们可以先将这个立方体翻转使得a<=b<=c,然后2^a枚举哪些面消毒哪些面不消毒,对于一个消毒的面答案是1,不消毒的面就是这个面上y和z做二分图最大匹配了。

复杂度O(2^17*17*nm)

然后我就这么写了,然后被卡TLE。

这题还有的极大的坑点,不能无脑memset,只能把每次需要用的地方重置,在做二分图匹配的时候可以用一个变量来代替每次memset vis数组。

然后我洛谷过了,bzoj被那两组新加数据继续卡常,f**k。

先上洛谷AC的代码吧,bzoj的应该可以改成非递归的二进制枚举来进一步减小常数。

代码:

#include<bits/stdc++.h>
#define maxn 6005
#define maxm 60005
using namespace std;
typedef long long LL;
int read()
{
    char c;int sum=0,f=1;c=getchar();
    while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0' && c<='9'){sum=sum*10+c-'0';c=getchar();}
    return sum*f;
}
int T;
int a,b,c;
int head[maxn],cnt;
struct Node{
    int nex,to;
}edge[maxm];
void add(int u,int v)
{
    edge[++cnt].to=v;
    edge[cnt].nex=head[u];
    head[u]=cnt;
}
struct point{
    int x,y,z;
    point(){}
    point(int _x,int _y,int _z):x(_x),y(_y),z(_z)
    {
        if(b<=a&&b<=c) swap(x,y);
        else if(c<=a&&c<=b) swap(z,x);
    }
}p[maxn];
int vis[maxn],match[maxn],tot_vis,tot;
bool dfs(int x)
{
    for(int i=head[x];i;i=edge[i].nex)
    {
        int nex=edge[i].to;
        if(vis[nex]==tot_vis) continue;
        vis[nex]=tot_vis;
        if(!match[nex] || dfs(match[nex]))
        {
            match[nex]=x;
            return true;
        }
    }
    return false;
}
int ans;
bool vis_x[maxn];
void solve(int pos,int sum)
{
    if(sum>=ans) return;
    if(pos>a)
    {
        for(int i=0;i<=b;i++) head[i]=0,match[i]=0;
        cnt=0;
        for(int i=1;i<=tot;i++)
        if(!vis_x[p[i].x])
        add(p[i].y,p[i].z);
        for(int i=1;i<=b;i++)
        {
            tot_vis++;
            if(dfs(i)) sum++;
            if(sum>=ans) return;
        }
        ans=sum;
        return;
    }
    
    vis_x[pos]=1;solve(pos+1,sum+1);
    vis_x[pos]=0;solve(pos+1,sum);
}
int main()
{
    T=read();
    while(T--)
    {
        tot=0;
        a=read();b=read();c=read();
        for(int i=1;i<=a;i++)
        for(int j=1;j<=b;j++)
        for(int k=1;k<=c;k++)
        {
            int x=read();
            if(x)
            p[++tot]=point(i,j,k);
        }
        if(b<=a&&b<=c) swap(a,b);
        else if(c<=a&&c<=b) swap(a,c);
        ans=a;
        solve(1,0);
        printf("%d\n",ans);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值