ZOJ3209 Treasure Map(DLX精确覆盖)

1 篇文章 0 订阅

ZOJ3209 Treasure Map

原题地址
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3209

题意:
T组数据。
给出一个n*m的矩形,给出p个小矩形(所在的左下角和右上角的坐标),求覆盖大矩形至少需要多少个小矩形(小矩形间不能重复覆盖)

数据范围
T <= 500,1 <= n, m <= 30, 1 <= p <= 500
0 <= x1 < x2 <= n, 0 <= y1 < y2 <= m

题解:

一篇好的DLX教程
这题就是把大矩形拆成n*m个小格子,就是DLX的列数,每个小矩形是一行,相应的位置加上点。

因为要求最小答案,还是要暴搜的,于是要剪枝,每次从残余行最少的列开始。


精确覆盖:
首先选择当前要覆盖的列(含1最少的列),将该列和能够覆盖到该列的行全部去掉,再枚举添加的方法。
枚举某一行r,假设它是解集中的一个,那么该行所能覆盖到的所有列都不必再搜,所以删除该行覆盖到的所有列,又由于去掉的列相当于有解,所以能够覆盖到这些列的行也不用再搜,删之。
重复覆盖:
首先选择当前要覆盖的列(同上),将该列删除,枚举覆盖到该列的所有行:对于某一行r,假设它是解集中的一个,那么该行所能覆盖到的列都不必再搜,所以删除该行覆盖到的所有列。
注意此时不用删去覆盖到这些列的行,因为一列中允许有多个1。
——摘自此博客

因为DLX本质上是个暴力,而重复覆盖的剪枝力度更小,重复覆盖往往需要估价函数来优化。


代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=500000;
const int M=505;
int T;
int R[N],L[N],U[N],D[N],S[N],H[M],col[N],row[N],tail=0,ans;
void init(int n,int m)
{
    for(int i=0;i<=m;i++)
    {R[i]=i+1; L[i+1]=i;U[i]=D[i]=i;S[i]=0;}
    L[0]=m; R[m]=0;
    for(int i=1;i<=n;i++) H[i]=-1;
}
void link(int r,int c)
{
    int nd=++tail;  
    row[nd]=r; col[nd]=c;
    D[U[c]]=nd; U[nd]=U[c]; D[nd]=c; U[c]=nd;
    if(H[r]==-1) {H[r]=nd; L[nd]=R[nd]=nd;}
    else
    {R[L[H[r]]]=nd;L[nd]=L[H[r]];L[H[r]]=nd; R[nd]=H[r];}
    S[c]++;
}
void remove(int x)
{
    R[L[x]]=R[x]; L[R[x]]=L[x];
    for(int i=D[x];i!=x;i=D[i])
    {
        for(int j=R[i];j!=i;j=R[j])
        {  
            D[U[j]]=D[j]; U[D[j]]=U[j];
            S[col[j]]--;
        }
    }
}
void resume(int x)
{
    R[L[x]]=x; L[R[x]]=x;
    for(int i=U[x];i!=x;i=U[i])
    {
        for(int j=L[i];j!=i;j=L[j])
        {
            D[U[j]]=j; U[D[j]]=j;
            S[col[j]]++;
        }
    }
}
void DLX(int k)
{   
    if(ans!=-1&&k>ans) return;
    if(!R[0]){ans=k; return;}

    int pos=R[0];
    for(int i=R[0];i;i=R[i])
    if(S[i]<S[pos]) pos=i;  
    remove(pos);
    for(int i=D[pos];i!=pos;i=D[i])
    {
        for(int j=R[i];j!=i;j=R[j])
        remove(col[j]);
        DLX(k+1);
        for(int j=L[i];j!=i;j=L[j])
        resume(col[j]);
    }
    resume(pos);
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        int n,m,p; scanf("%d%d%d",&n,&m,&p);
        init(p,n*m);tail=n*m;
        for(int i=1;i<=p;i++)
        {
            int X1,Y1,X2,Y2; scanf("%d%d%d%d",&X1,&Y1,&X2,&Y2);
            for(int x=X1+1;x<=X2;x++)
            for(int y=Y1+1;y<=Y2;y++)
            link(i,(x-1)*m+y);
        }
        ans=-1;
        DLX(0);
        printf("%d\n",ans);
    }
    return 0;
}       
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值