紫书 第7章 暴力求解法 习题7-10 Guarding the Chessboard UVA - 11214

博客详细分析了八皇后问题的一个变种,探讨了使用迭代加深(ID)算法解决此问题的效率。通过算法速度分析,讨论了循环次数、状态变化和解的检查速度,并提出了优化策略,包括减少状态变化的时间复杂度和循环次数优化。此外,介绍了如何通过预先设定最大循环次数来进一步加速求解过程。博客提供了AC代码实现,并总结了问题的关键在于算法速度分析和对八皇后问题的掌握。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

https://vjudge.net/status#un=&OJId=UVA&probNum=11214&res=0&orderBy=run_id&language=

https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2155

这道题目是经典的八皇后问题八皇后问题的一个衍生。
虽然问题和原八皇后问题有点出入,但是解题思路基本上还是一致的。

分析

首先,根据例题给的数据当8X8格子填满的时候的解是5。这里我们已经可以看到解的数量是远远小于整个图的状态(8X8 = 64)的。根据这个我们就已经能狗很好的推断这道题选用迭代加深(ID)算法会是一个很好的选择,因为长和宽的上限不超过9。

算法速度分析

这个是迭代加深的一个模版伪代码

bool bt(int d, int maxd) {
    if (maxd == d) {
		if (检查有解()return truereturn false}
    循环所有状态 {
       保存当前状态();
       改变当前状态到可能的下一次状态();
       if(bt(d + 1, maxd)) return true;
       改回当前状态();
    }
    return false;
}

int main() {
    for (int maxd = 0;; maxd++) {
        if (bt(0, maxd, 0, 0)) {
            打印答案();
            break;
        }
    }
    return 0;
}

从中我们能看出能够影响迭代加深的速度的几个因素:

  1. 循环所有状态的次数
  2. 改变状态需要消耗的速度
  3. 检查解的速度,这个速度相对比2)影响要小一些,因为只用考虑(d == maxd)的情况。
循环次数

首先,不管给什么样的grid,假设这道题grid的高是n,宽m,解是x,所有可能解的状态就等于在m*n的格子里选择x个格子放皇后。直接套用组合数公式就能算出总共需要
C x m ∗ n = ( m ∗ n ) ! ( m ∗ n − x ) ! x ! C_x^{m*n} = \frac {(m*n)!} {(m*n- x)!x!} Cxmn=(mnx)!x!(mn)!
次循环。
根据给的限制条件,我们知道最大可能的情况是9X9的格子里全部被mark了,因为已知8X8的解是5,很容易就能推出9X9的解不是5就是6(最终算出来发现是5)。
我们先假设是6,根据上边推出的公式算出最大可能循环次数
81 ∗ 80 ∗ 79 ∗ 78 ∗ 77 ∗ 76 6 ∗ 5 ∗ 4 ∗ 3 ∗ 2 ∗ 1 = 324540216 \frac {81*80*79*78*77*76} {6*5*4*3*2*1} =324540216 654321818079787776=324540216
次。
但是实际上因为我们找到解就返回了,而在有6个皇后的情况下,第一个皇后的位置肯定在第一行第一个(这个是根据8X8有五个解的情况推出来的)这样的话,其实最慢速度就是完全循环完5解 81 ∗ 80 ∗ 79 ∗ 78 ∗ 77 5 ∗ 4 ∗ 3 ∗ 2 ∗ 1 = 25621596 \frac {81*80*79*78*77} {5*4*3*2*1} =25621596 543218180797877=25621596次。这个循环次数对于给定的限时6666ms来说已经非常能够接受了。而真正的跑的时候的速度其实比这个还要快大概5-10倍,因为最大解只有5次,而第一个皇后很有可能在第一行。所以一个皇后放在第一行就会停止了。

改变状态和检查解的速度

如果我们只考虑最简单的算法:每次放皇后都清除掉放在路径上的X,检查的时候看这个grid是不是空的。这两个都需要完全循环整个grid,也就是说,最慢的速度就是长乘以宽9X9=81次。基于上边分析,不负责任的推测这个速度可能是可以跑完这个题的。(笔者并没有试过)

优化

改变状态和检查解的速度优化

这道题的优化思路来源于八皇后问题。每次放皇后我们并不需要改变grid的状态,而是可以直接标记皇后有经过当前点的行,列,对角线和反对角线。这样的话改变状态的时间效率直接从O(mxn)减少到了O(4)。而检查解的时候我们只用检查是不是每个X的位置上有没有任意的行,列,对角线和反对角线被标记过,如果没有直接返回false。这样虽然还是要循环mxn次,但是状态还是有一定的减少。

循环次数优化

如果所在的行,列,对角线和反对角线都被标记过,当前位置可以不用标记,这个条件我感觉加上会有一定的效果,不过效果有限。

耍赖皮的优化方法

如果为了提高速度,因为已知最大的答案是5,可以只用循环到4,如果没找到解直接输出结果5。这样最后最慢的那次循环可以不用做。

AC代码

这个是不耍赖皮的版本。如果想要耍赖皮,请读者自己实验

#include<bits/stdc++.h>

using namespace std;
int n, m;
bool grid[10][10], r[10], c[10], di[20], rd[20];

bool bt(int d, int maxd, int x, int y) {
    if (maxd == d) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (grid[i][j] && !r[i] && !c[j] && !di[i + j] && !rd[i - j + m - 1]) return false;
            }
        }
        return true;
    }
    for (int i = x; i < n; i++) {
        for (int j = ((i == x) ? y : 0); j < m; j++) {
            bool cr = r[i], cc = c[j], cd = di[i + j], crd = rd[i - j + m - 1];
            if (cr && cc && cd && crd) continue; // all marked, just go next
            r[i] = c[j] = di[i + j] = rd[i - j + m - 1] = 1;
            int ni = i, nj = j + 1;
            if (nj == m) nj = 0, ni++;
            if(bt(d + 1, maxd, ni, nj)) return true;
            r[i] = cr, c[j] = cc, di[i + j] = cd, rd[i - j + m - 1] = crd;
        }
    }
    return false;
}

int main() {
    int kase = 1; 
    while(cin >> n >> m && n && m) {
        getchar();
        for (int i = 0; i < n ;i++) {
            for (int j = 0; j < m; j++) grid[i][j] = (getchar() == 'X');
            getchar();
        }
        
        for (int maxd = 0;; maxd++) {
            memset(r, 0, sizeof(r));
            memset(c, 0, sizeof(c));
            memset(di, 0, sizeof(di));
            memset(rd, 0, sizeof(rd));
            if (bt(0, maxd, 0, 0)) {
                printf("Case %d: %d\n", kase++, maxd);
                break;
            }
        }
    }
    return 0;
}

小结

这题比起我前几遍博客里边的题,这道题算比较简单了。这道题的关键在于算法的速度分析,只要能够推算出大概速度,最后写出的解也是比较有底气的。对于优化方法也是完全是借鉴了八皇后问题 ,只要把那道经典题掌握了,想出解来并不难。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值