您的位置:首页 > 其它

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数组)。

#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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: