[bzoj3140][Hnoi2013]消毒——枚举+最大二分图匹配

针对三维长方体中特定格子的消毒问题,通过转换为最小点覆盖问题进行求解。文章介绍了如何将问题简化为二维情况,并进一步扩展到三维的情况,通过枚举和最小点覆盖算法找到最优解。

题目大意:

一个长方体中有一些格子需要消毒,消毒一个abca∗b∗c的格子需要的费用为min(a,b,c)min(a,b,c)问将所有的格子都消毒的最下费用为多少。

思路:

先来考虑二维下怎么消毒:先确定计算费用的那一维度,然后剩下的一维就拓展到低,所以最后的形式必定是xbx∗b或者axa∗x。对于xbx∗b或者axa∗x这种形式,我们把它拆开,分为xx1b或者xxa1,然后用这些长条当做点取覆盖用格子构成的边,这样跑最小点覆盖,答案一定是最优且符合条件的。我一开始想xbx∗bx>bx>b的时候我们还是用xx个条形去覆盖的话那岂不是错了,发现这种情况下跑最小点覆盖的话用b个a1反而会更优,即最小点覆盖完美地避免了这种情况。
那么三维的也就是类似了,大概就是三种点ab1,a1c,1bca∗b∗1,a∗1∗c,1∗b∗c,对于每一条边(x,y,z)(x,y,z)它要将三个点互相连在一起,然后只要有一个点选了这条边就算覆盖了,然后求最小点覆盖。发现不会做这种清奇的模型,发现最小的一维只有17,可以217217暴力枚举最小的那一维选择那些点然后剩下的两维对于空间中未被前一维覆盖的点跑最小点覆盖就好了。
注意代码的优化,在枚举的时候不要重新建图,建图的时候状压下每一条边(边所代表的两个面的交集就是一维的)中间所要消毒的部分,然后在枚举的时候判断一下子集就好了。
要卡常。。。

/*=================================
 * Author : ylsoi
 * Problem : bzoj3140
 * Algrithm : Max Node Covering
 * Time : 2018.6.16
 * ==============================*/
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<climits>
#include<ctime>
using namespace std;
void File(){
    freopen("bzoj3140.in","r",stdin);
    freopen("bzoj3140.out","w",stdout);
}
template<typename T>bool chkmax(T &_,T __){return _<__ ? (_=__,1) : 0;}
template<typename T>bool chkmin(T &_,T __){return _>__ ? (_=__,1) : 0;}
#define REP(i,a,b) for(register int i=a;i<=b;++i)
#define DREP(i,a,b) for(register int i=a;i>=b;--i)
#define MREP(i,x) for(register int i=beg[x];i;i=E[i].last)
#define mem(a) memset(a,0,sizeof(a))
#define ll long long
#define inf INT_MAX
#define A(x) ((int)ceil(x*1.0/(b*c)))
#define B(x) ((int)ceil(((x-1)%(b*c)+1)*1.0/b))
#define C(x) (((x-1)%(b*c))%b+1)
#define F(aa,bb,cc) ((aa-1)*b*c+(bb-1)*b+cc)
const int maxn=5000+10;
int D,a,b,c,beg[maxn],cnte,num[maxn][maxn],ans=inf;
bool mp[maxn];
struct edge{int to,last;}E[maxn*2];
void add(int u,int v){
    ++cnte;
    E[cnte].to=v;
    E[cnte].last=beg[u];
    beg[u]=cnte;
}
void init(){
    scanf("%d%d%d",&a,&b,&c);
    int len=a*b*c;
    REP(i,1,len)cin>>mp[i];
    if(a<=b && a<=c){
        REP(j,1,b)REP(k,1,c){
            REP(i,1,a)if(mp[F(i,j,k)])
                num[j][k]|=1<<(i-1);
            if(num[j][k])add(j,k);
        }
    }
    else if(b<=a && b<=c){
        REP(i,1,a)REP(k,1,c){
            REP(j,1,b)if(mp[F(i,j,k)])
                num[i][k]|=1<<(j-1);
            if(num[i][k])add(i,k);
        }
    }
    else if(c<=a && c<=b){
        REP(i,1,a)REP(j,1,b){
            REP(k,1,c)if(mp[F(i,j,k)])
                num[i][j]|=1<<(k-1);
            if(num[i][j])add(i,j);
        }
    }
}
int be[maxn],cnt_vis,vis[maxn],S0,cnt_be,bb[maxn];
int Hungary(int u){
    MREP(i,u){
        int v=E[i].to;
        if((num[u][v]|S0)==S0)continue;
        if(vis[v]==cnt_vis)continue;
        vis[v]=cnt_vis;
        if(bb[v]!=cnt_be || Hungary(be[v])){    
            be[v]=u;
            bb[v]=cnt_be;
            return true;
        }
    }
    return false;
}
int cal(int t){
    int ret=0;
    ++cnt_be;
    DREP(i,t,1){
        ++cnt_vis;
        ret+=Hungary(i);
    }
    return ret;
}
void work(){
    if(a<=b && a<=c){
        for(S0=(1<<a)-1;S0>=0;--S0)
            chkmin(ans,__builtin_popcount(S0)+cal(b));
    }
    else if(b<=a && b<=c){
        for(S0=(1<<b)-1;S0>=0;--S0)
            chkmin(ans,__builtin_popcount(S0)+cal(a));
    }
    else if(c<=a && c<=b){
        for(S0=(1<<c)-1;S0>=0;--S0)
            chkmin(ans,__builtin_popcount(S0)+cal(a));
    }
}
int main(){
    File();
    scanf("%d",&D);
    while(D--){
        ans=inf;
        mem(beg);
        mem(num);
        cnte=0;
        init();
        work();
        printf("%d\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值