POJ 2032 搜索 (IDA*) 或 DLX
2015-10-07 16:32
387 查看
题意:
你有一间矩形的屋子,屋子的地面上铺着正方形的地板,共有 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数组)。
哎,国庆这些天的培训,每次考试都是被虐,感觉快没脸参加noip了。最后杜神的搜索题感觉都不算很难的样子,但是自己写不出来,考场上想不出来。这几天就先练练搜索了。
2015.10.14
然而,自己yy的DLX代码可以用后就来重写了一下这道题,下面是DLX版本。
不过暂时没加估价函数来剪枝,导致跑的比IDA*要慢一些。(16Ms 与 172Ms)
你有一间矩形的屋子,屋子的地面上铺着正方形的地板,共有 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; }
相关文章推荐
- css position 应用(absolute和relative用法)
- ETL 浅谈
- GCT学习总结
- Maven学习总结(四)——Maven核心概念
- 宗宁:如何工作一年获得三年的经验
- 约瑟夫环问题
- GCT学习总结
- hadoop eclipse windows
- asp.net(C#)页面事件顺序
- iOS ----Block语法
- 理解数据库的几种键和几个范式
- centos 6.5 搭建dhcp服务器
- logistic回归
- 【自考】软件开发工具—现状与发展、使用与开发、过程及组织
- HTML5学习之css3.0
- No1 程序启动原理
- 【开启Java之旅】
- 3D频率谱线表
- leetcode面试准备: Word Pattern
- adb getprop setprop watchprop用法