POJ 2032 搜索 (IDA*) 或 DLX

题意:

你有一间矩形的屋子,屋子的地面上铺着正方形的地板,共有 H 行 W 列。经过长时间的使用,一些地板损坏了。而你又没有钱将整间屋子的地板都换成新的,所以你决定用一些地毯将损坏的地毯盖起来。
然而,你需要遵守下面的注意事项:
所有的地毯都必须是正方形的。
地毯可以相互覆盖。
地毯可以是任意大小的。
地毯不能盖到屋子外面。
所有的损坏的地板都要被盖住。
所有的未损坏的地板都不能被盖住。

这道题本来是暴搜加很多剪枝,自己原来的T了,看了网上题解有IDA*就被吓到了,因为之前没有写过IDA*,而且对于IDA*还处于一个不了解的状态。不过看到也有向我一样写暴搜加剪枝的题解,其中有几个我没用到的剪枝,之后加在我的代码中交上去却W了,拍一拍也没发现什么问题,但是不想调了,决定学一下IDA*。

/*
先看了看刘汝佳叔叔的紫书,在IDDFS的介绍中加了一行,就是提到了IDA*,说这个是表达一下IDA*并没有A*那么悚[自己蒟蒻]听闻,IDA*可以大概理解为IDDFS+A*的h函数。IDDFS是在DFS的基础上,限制搜索的深度,当搜索深度超过预定值Maxd时就return,在搜到解之前不断加大Maxd,直到搜索到解。Maxd初值很小(一般从0开始循环)。
然而在这个迭代加深搜索的过程中加一个估价函数h,就可以说是IDA*了,h函数的作用是估算当前状态到达解需要多少步,而如果当前已走步数(或深度)加上这个估算值大于已知ans的话就可以return了。
*/

以上只是很浅显的一个理解,感觉这道题并不能算是IDA*的一个很好的例题,它单单是在搜索的过程中加了h函数,并没有体现”ID”,自己在理解上也有一些问题:IDDFS搜索任一可行解以及DFS+h函数搜索最优解可以很好理解,但IDA*怎么搜这两种解感觉还有些问题,等下再写一道经典一点的例题加深一下理解吧。

那么来看这道题的做法:
我是先给所有点都编了号,id(x, y),这样数组可以减小维度,
先预处理出以每个点为左上角的最大地毯,f数组
DP(O(N^2)),或者用二维树状数组(O(N^2 log^2N))
还需要预处理出要覆盖每个点,需要哪些地毯,记录编号,cov数组

问题就变成,对于所有的地毯,枚举铺设哪些,使得在最少块数下覆盖所有的1。

可以做的一个优化就是对于两块可以铺盖的地毯i,j(i,j表示点的编号),如果i可以完全包含j,那么选i一定比比选j差,因为地毯是可以覆盖的,所以选尽量大的总是好的。这样我们搜索时就不需要枚举j了。

h函数:这个函数是要满足一定条件的估价,刘汝佳叔叔称为“乐观估价”,因为当now+h() >= ans时我们会return,那么h的估价一定不能比真实情况大,而且为了保证效果又不能比真实情况小太多。所以h函数一般需要好好想一想。对于这道题不错的一个估价是:当前状态下,遍历一遍所有的点,对于还没有被覆盖的,我们把所有可以覆盖这个点的地毯都铺上,这些地毯算为1块。最后计算出的当前状态下还需要铺的“地毯数”就是h函数返回值。这个结果一定是比真实情况小的,但也不会小很多,性能还是不错的。

之后就可以写搜索了,一开始自己写的还是有问题,之后看了一份题解,换成人家的搜索姿势就A了。[泪]
我的搜索姿势是:当前点t没有被覆盖时,一定要铺一块地毯覆盖这个点,所以就铺t这块地毯,若已经被覆盖,那么就可以不铺,但是还是要搜索铺一块的情况,不过剪枝:如果要再铺一块的话没有任何效果那么就不铺。
别人家的搜索姿势:当前点被覆盖时,不需要再铺,只搜索不铺,若没被覆盖,枚举用哪一块来覆盖(cov数组)。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int n, m, ans, f[115], num[115], cov[115][115];
bool map[115], vis[115], vis2[115]; 

int id(int x, int y){
    return (x-1)*m+y;
}

bool cover(int t){
    if(!map[t]) return 1;
    for(int i = 1; i <= num[t]; i++){
        if(vis[cov[t][i]]) return 1;
    }
    return 0;
}

int h(){
    int res = 0;
    for(int i = id(n, m); i; i--){
        vis2[i] = vis[i];
    }
    for(int i = 1; i <= n; i++)
    for(int j = 1; j <= m; j++){
        int t = id(i, j);
        if(cover(t)) continue;
        res++;
        for(int k = 1; k <= num[t]; k++){
            vis[cov[t][k]] = 1;
        }
    }
    for(int i = id(n, m); i; i--){
        vis[i] = vis2[i];
    }
    return res;
}

void sec(int t, int ed){
    if(ed + h() >= ans) return ;
    if(t > id(n, m)){
        ans = min(ans, ed); return ;
    }
    if(!map[t]){sec(t+1, ed); return ;}
    if(cover(t)){
        sec(t+1, ed); return ;
    }
    for(int i = 1; i <= num[t]; i++){
        vis[cov[t][i]] = 1;
        sec(t+1, ed+1);
        vis[cov[t][i]] = 0;
    }
}

int main()
{
    while(scanf("%d %d", &m, &n) && n){
        memset(  f, 0, sizeof   f);
        memset(map, 0, sizeof map);
        memset(cov, 0, sizeof cov);
        memset(num, 0, sizeof num);
        memset(vis, 0, sizeof vis);

        for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++){
            int t = id(i, j);
            scanf("%d", map+t);
        }

        for(int i = n; i; i--){
            f[id(i, m)] = map[id(i, m)];
            for(int j = m-1; j; j--){
                int t = id(i, j); if(map[t])
                f[t] = min(f[t+m], min(f[t+1], f[t+m+1])) + 1;
            } 
        }

        for(int i = n; i; i--)
        for(int j = m; j; j--){
            int t = id(i, j);
            if(f[t+1] < f[t]) f[t+1] = 0;
            if(f[t+m] < f[t]) f[t+m] = 0;
            if(f[t+m+1] < f[t]) f[t+m+1] = 0;
        }

        ans = 0;
        for(int i = n; i; i--)
        for(int j = m; j; j--){
            int t = id(i, j);
            if(f[t]) ans++;
            for(int k = 0; k < f[t]; k++)
            for(int l = 0; l < f[t]; l++){
                int t2 = id(i+k, j+l);
                cov[t2][++num[t2]] = t;
            }
        }

        sec(1, 0);
        printf("%d\n", ans);
    }
    return 0;
}

哎,国庆这些天的培训,每次考试都是被虐,感觉快没脸参加noip了。最后杜神的搜索题感觉都不算很难的样子,但是自己写不出来,考场上想不出来。这几天就先练练搜索了。

2015.10.14
然而,自己yy的DLX代码可以用后就来重写了一下这道题,下面是DLX版本。
不过暂时没加估价函数来剪枝,导致跑的比IDA*要慢一些。(16Ms 与 172Ms)

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;

int n, m, Row, Col, f[15][15], id[15][15];
bool map[15][15], mtr[105][105];

struct DLX{

    int sz, ans, ele, col[1005], row[1005], del[1005];
    int U[1005], D[1005], L[1005], R[1005], S[105];

    void init(int c)
    {
        ans = 1<<30; sz = ele = c;
        memset(U, 0, sizeof U);
        memset(D, 0, sizeof D);
        memset(L, 0, sizeof L);
        memset(R, 0, sizeof R);
        memset(S, 0, sizeof S);
        memset(del, 0, sizeof del);

        for(int i = 0; i <= ele; i++)
        {
            U[i] = D[i] = i;
            L[i] = i-1; R[i] = i+1;
            row[i] = i;
        }
        L[0] = sz; R[sz] = 0;
    }

    void add_col(int c, bool *co)
    {
        int fir = sz;
        for(int i = 1; i <= ele; i++) if(co[i])
        {
            sz++; S[i]++;
            D[sz] = i; U[sz] = U[i];
            D[U[i]] = sz; U[i] = sz; 
            L[sz] = sz-1; R[sz] = sz+1;
            col[sz] = c; row[sz] = i;
        }
        if(fir != sz) L[fir+1] = sz, R[sz] = fir+1;
    }

    void remove(int c)  
    {  
        int t = c;
        do
        {
            int tt = row[t];
            if(!del[tt]) L[R[tt]] = L[tt], R[L[tt]] = R[tt], del[tt] = col[c];
            t = R[t];
        }   while(t != c);
    }  

    void resume(int c)  
    {     
        int t = c;
        do
        {
            int tt = row[t];
            if(del[tt] == col[c]) L[R[tt]] = tt, R[L[tt]] = tt, del[tt] = 0;
            t = R[t];
        }   while(t != c);
    }

    void dfs(int sum)
    {
        if(sum  >= ans) return ;
        if(!R[0]) { ans = sum; return ;}

        int r, Mins = 1<<30;
        for(int j = R[0]; j; j = R[j]) if(S[j] < Mins)
        {
            Mins = S[j], r = j;
        }   

        for(int i = D[r]; i != r; i = D[i])
        {
            remove(i);
            dfs(sum + 1);
            resume(i);
        }
    }

    void solve()
    {
        dfs(0);
        if(ans < (1<<30)) printf("%d\n", ans);
        else printf("-1\n");
    }
};

int main()
{
    while(scanf("%d %d", &m, &n) && n && m)
    {
        memset(  f, 0, sizeof   f);
        memset( id, 0, sizeof  id);
        memset(map, 0, sizeof map);
        memset(mtr, 0, sizeof mtr);

        Row = Col = 0;
        for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
        {
            scanf("%d", map[i]+j);
            if(map[i][j]) id[i][j] = ++Row;
        }

        for(int i = n; i; i--)
        for(int j = m; j; j--) if(map[i][j])
        {
            f[i][j] = min(f[i][j+1], min(f[i+1][j], f[i+1][j+1])) + 1;
        }

        for(int i = n; i; i--)
        for(int j = m; j; j--)
        {
            if(f[i][j] > f[i+1][j]) f[i+1][j] = 0;
            if(f[i][j] > f[i][j+1]) f[i][j+1] = 0;
            if(f[i][j] > f[i+1][j+1]) f[i+1][j+1] = 0;
        }

        for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++) if(f[i][j])
        {
            Col++;
            for(int k = i; k-i < f[i][j]; k++)
            for(int l = j; l-j < f[i][j]; l++)
            {
                mtr[Col][id[k][l]] = 1;
            }
        }

        DLX T;
        T.init(Row);
        for(int i = 1; i <= Col; i++)
        {
            T.add_col(i, mtr[i]); 
        }
        T.solve();
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值