洛谷P2622 关灯问题II引发的关于DP实现形式及后效性的思考

  动态规划要求已经求解的子问题不受后续阶段的影响,即无后效性。而在这种递推的实现方式中,后面枚举的状态可能更新前面已经枚举过的状态。也就是说,这种递推的实现方式是具有后效性的。
以这组数据为例

3
3
1 -1 1 
0 1 -1 
0 0 1

  正解应为 \(3\) 次(\(111 \to 010 \to 100 \to 000\)) 。

  而在递推时,\(100\)\(010\) 之前被已被枚举,在 \(010\) 更新了 \(100\) 之后无法再通过 \(100\) 来得到目标状态 \(000\),便会得出 \(-1\)(无解)的错误答案。

//递推
#include <stdio.h>
#include <string.h>
#include <algorithm>
using std::min;

int m, n, a[110][10], f[1 << 10];

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; ++i) {
        for (int j = 0; j < n; ++j) {
            scanf("%d", &a[i][j]);
        }
    }
    memset(f, 0x3f, sizeof(f));
    f[(1 << n) - 1] = 0;
    for (int i = (1 << n) - 1; i >= 0; --i) {
        for (int k = 1; k <= m; ++k) {
            int t = i;
            for (int j = 0; j < n; ++j) {
                if (a[k][j] == 1 && (i & (1 << j))) t ^= 1 << j;
                if (a[k][j] == -1 && !(i & (1 << j))) t ^= 1 << j;
            }
            f[t] = min(f[t], f[i] + 1);
        }
    }
    printf("%d", f[0] == 0x3f3f3f3f ? -1 : f[0]);
    return 0;
}

  对于该题,适合使用记忆化BFS的实现方式。用BFS来进行记忆化搜索的这种实现方式可能比较少见,其实只是对于状态的遍历顺序不同。由于BFS的特性,每个状态在第一次被扩展到时便得到最优解。

//记忆化BFS
#include <stdio.h>
#include <string.h>
#include <algorithm>
using std::min;

int m, n, head, tail, q[10000], a[110][10], f[1 << 10];
bool vis[1 << 10];

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; ++i) {
        for (int j = 0; j < n; ++j) {
            scanf("%d", &a[i][j]);
        }
    }
    memset(f, 0x3f, sizeof(f));
    f[(1 << n) - 1] = 0;
    q[tail++] = (1 << n) - 1;
    vis[(1 << n) - 1] = true;
    while (head < tail) {
        int cur = q[head++];
        for (int i = 1; i <= m; ++i) {
            int t = cur;
            for (int j = 0; j < n; ++j) {
                if (a[i][j] == 1 && (cur & (1 << j))) t ^= 1 << j;
                if (a[i][j] == -1 && !(cur & (1 << j))) t ^= 1 << j;
            }
            if (vis[t]) continue;
            vis[t] = true;
            f[t] = f[cur] + 1;
            q[tail++] = t;
            if (t == 0) {
                printf("%d", f[t]);
                return 0;
            }
        }
    }
    printf("-1");
    return 0;
}

  当然,使用DFS+剪枝也可较为高效的解决该题。不过终究还是会重复遍历一些状态,比DP略慢。

posted @ 2022-08-15 18:52  东方澂  阅读(35)  评论(0编辑  收藏  举报