【动态规划】插头dp学习笔记

题目

给你一个 n × m n \times m n×m 的棋盘,有的格子是障碍,问共有多少条回路满足经过每个非障碍格子恰好一次。

在这里插入图片描述

如图, n = m = 4 n=m=4 n=m=4 ( 1 , 1 ) , ( 1 , 2 ) (1,1),(1,2) (1,1),(1,2) 是障碍,共有 2 2 2 条满足要求的回路。

输入格式

第一行包含两个整数 n , m n,m n,m

接下来 n n n 行,每行包含一个长度为 m m m 的字符串,字符串中只包含 *.,其中 * 表示障碍格子,. 表示非障碍格子。

输出格式

输出一个整数,表示满足条件的回路数量。

数据范围

2 ≤ n , m ≤ 12 2 \le n,m \le 12 2n,m12

输入样例:
4 4
**..
....
....
....
输出样例:
2

思路

在这里插入图片描述
在这里插入图片描述

实现代码(详细注释)

#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
const int N = 50000, M = N * 2 + 7;
int n, m;
int endx, endy; //  记住最后一个有效格子便于封口
int g[20][20];
int q[2][N];    //  滚动数组,即有效状态在哈希表里面的下标
int cnt[N]; //  每次滚动时有效状态的数量
int h[2][M];    //  滚动哈希表
ll v[2][M]; //  哈希表存的方案数(对应的值)
int find(int cur, int x) {  //  开放寻址法 求x在哈希表中的位置
    int t = x % M;
    while (h[cur][t] != -1 && h[cur][t] != x) 
        if ( ++ t == M) 
            t = 0;
    return t;
}
void insert(int cur, int state, ll w) { //  在当前滚动的状态内插入state,数量为w
    int t = find(cur, state);
    if (h[cur][t] == -1) {
        h[cur][t] = state, v[cur][t] = w;
        q[cur][ ++ cnt[cur] ] = t;
    } else v[cur][t] += w;  //  状态在哈希表内已经存在
}
int get(int state, int k) { //  求第k个各自的状态,即四进制的第k位数字
    return state >> k * 2 & 3;
}
int set(int k, int v) { //  构造四进制的第k位数字为v的数
    return v * (1 << k * 2);
}
int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++ i ) {
        char str[20];
        scanf("%s", str + 1);
        for (int j = 1; j <= m; ++ j ) 
            if (str[j] == '.') {
                g[i][j] = 1;
                endx = i, endy = j;
            }
    }
    ll res = 0;
    memset(h, -1, sizeof h);
    int cur = 0;
    insert(cur, 0, 1);
    for (int i = 1; i <= n; ++ i ) {
        for (int j = 1; j <= cnt[cur]; ++ j ) //    处理分界线,0...... -> ......0
            h[cur][q[cur][j]] <<= 2;    //  每一次做下一行时,将上一行左移一位(相当于二进制下左移两位)
        for (int j = 1; j <= m; ++ j ) {
            int last = cur;
            cur ^= 1, cnt[cur] = 0;
            memset(h[cur], -1, sizeof h[cur]);
            for (int k = 1; k <= cnt[last]; ++ k ) {
                int state = h[last][q[last][k]];
                ll w = v[last][q[last][k]];
                int x = get(state, j - 1), y = get(state, j);
                if (!g[i][j]) {
                    if (!x && !y) insert(cur, state, w);
                } else if (!x && !y) {
                    if (g[i + 1][j] && g[i][j + 1]) insert(cur, state + set(j - 1, 1) + set(j, 2), w);
                } else if (!x && y) {
                    if (g[i][j + 1]) insert(cur, state, w);
                    if (g[i + 1][j]) insert(cur, state + set(j - 1, y) - set(j, y), w);
                } else if (x && !y) {
                    if (g[i][j + 1]) insert(cur, state - set(j - 1, x) + set(j, x), w);
                    if (g[i + 1][j]) insert(cur, state, w);
                } else if (x == 1 && y == 1) {
                    for (int u = j + 1, s = 1; ; ++ u ) {
                        int z = get(state, u);
                        if (z == 1) s ++ ;
                        else if (z == 2) {
                            if ( -- s == 0) {   //  找到了和他配对的2
                                insert(cur, state - set(j - 1, x) - set(j, y) - set(u, 1), w);
                                break;
                            }
                        }
                    }
                } else if (x == 2 && y == 2) {
                    for (int u = j - 2, s = 1; ; -- u ) {
                        int z = get(state, u);
                        if (z == 2) s ++ ;
                        else if (z == 1) {
                            if ( -- s == 0) {
                                insert(cur, state - set(j - 1, x) - set(j, y) + set(u, 1), w);
                                break;
                            }
                        }
                    }
                } else if (x == 2 && y == 1) {
                    insert(cur, state - set(j - 1, x) - set(j, y), w);
                } else if (i == endx && j == endy) {    //  封口,必须是最后一个合法的格子,否则路径不合法
                    res += w;
                }
            }
        }
    }
    cout << res << '\n';
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值