P5056 【模板】插头dp

本文深入探讨了插头DP算法,一种高效的动态规划方法,用于解决特定类型的路径寻找问题。通过详细的代码解释和实例分析,文章揭示了算法背后的逻辑与应用技巧。

\(\color{#0066ff}{ 题目描述 }\)

给出n*m的方格,有些格子不能铺线,其它格子必须铺,形成一个闭合回路。问有多少种铺法?

\(\color{#0066ff}{输入格式}\)

第1行,n,m(2<=n,m<=12)

从第2行到第n+1行,每行一段字符串(m个字符),"*"表不能铺线,"."表必须铺

\(\color{#0066ff}{输出格式}\)

输出一个整数,表示总方案数

\(\color{#0066ff}{输入样例}\)

4 4
**..
....
....
....

\(\color{#0066ff}{输出样例}\)

2

\(\color{#0066ff}{数据范围与提示}\)

none

\(\color{#0066ff}{ 题解 }\)

插头DP本来以为多niubility的算法原来本质还是个DP,就是情况多了点qwq
状压分割线,有三种情况,无,左插头,右插头(详见洛谷题解懒得写了
只要明白状态分析出所有情况即可
#include<bits/stdc++.h>
#define LL long long
LL in() {
    char ch; LL x = 0, f = 1;
    while(!isdigit(ch = getchar()))(ch == '-') && (f = -f);
    for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 1) + (x << 3) + (ch ^ 48));
    return x * f;
}
std::unordered_map<LL, LL> f[2];
//我TM就不写hash表
int n, m, s, t;
bool mp[100][100];
char getch() {
    char ch = getchar();
    while(ch != '*' && ch != '.') ch = getchar();
    return ch;
}
void init() {
    n = in(), m = in();
    for(int i = 1; i <= n; i++) 
        for(int j = 1; j <= m; j++) {
            mp[i][j] = getch() == '.';
            if(mp[i][j]) s = i, t = j;
        }
}
LL pos(int v, int x) { return (v << (x << 1)); }
//返回第x大块的v状态(两个二进制来状压)
LL work() {
    int now = 0, nxt = 1;
    f[0][0] = 1;
    LL U = (1LL << ((m + 1) << 1)) - 1;
    //全集
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            f[nxt].clear();
            for(auto &k:f[now]) {
                LL S = k.first, val = k.second;
                LL L = (S >> ((j - 1) << 1)) & 3, R = (S >> (j << 1)) & 3;
                //分割线(L是当前竖着的那个,R是紧接着横着的那个)
                if(!mp[i][j]) {
                    if(!L && !R) f[nxt][S] += val;
                    continue;
                }
                // 0 0
                if(!L && !R) {
                    if(mp[i][j + 1] && mp[i + 1][j]) f[nxt][S ^ pos(1, j - 1) ^ pos(2, j)] += val;
                    //0 0 -> 1 2
                }
                //2 1
                else if(L == 2 && R == 1) {
                //2 1 -> 0 0
                    f[nxt][S ^ pos(L, j - 1) ^ pos(R, j)] += val;
                }
                // 0 1 // 1 0 // 0 2 // 2 0 
                else if(!L || !R) {
                    //0 1 // 0 2
                    if(!L) {
                        //拐弯
                        if(mp[i][j + 1]) f[nxt][S] += val;
                        //不拐弯
                        if(mp[i + 1][j]) f[nxt][S ^ pos(L, j - 1) ^ pos(L, j) ^ pos(R, j - 1) ^ pos(R, j)] += val;
                    }
                    //同上
                    else if(!R) {
                        if(mp[i][j + 1]) f[nxt][S ^ pos(L, j - 1) ^ pos(L, j) ^ pos(R, j - 1) ^ pos(R, j)] += val;
                        if(mp[i + 1][j]) f[nxt][S] += val;
                    }
                }
                //1 1 // 2 2
                else if(L == R) {
                    // 1 1
                    if(L == 1) {
                        int du = 0;
                        //1 1 -> 0 0 但是源头接口处右插头变成左插头
                        for(int p = j; ; p++) {
                            LL o = (S >> (p << 1)) & 3;
                            if(o == 1) du++;
                            if(o == 2) du--;
                            if(!du) {
                                //原来状态消去,弄上新状态
                                f[nxt][S ^ pos(L, j - 1) ^ pos(R, j) ^ pos(2, p) ^ pos(1, p)] += val;
                                break;
                            }
                        }
                    }
                    //根上面差不多
                    else if(L == 2) {
                        int du = 0;
                        for(int p = j - 1; ; p--) {
                            LL o = (S >> (p << 1)) & 3;
                            if(o == 1) du--;
                            if(o == 2) du++;
                            if(!du) {
                                f[nxt][S ^ pos(L, j - 1) ^ pos(R, j) ^ pos(2, p) ^ pos(1, p)] += val;
                                break;
                            }
                        }
                    }
                }
                //本状态当且仅当是终点,用于封口
                else if(L == 1 && R == 2 && i == s && j == t) return val;
            }
            std::swap(now, nxt);
        }
        f[nxt].clear();
        //末尾的竖分割线到了下一行就变成了行首的分割线,把状态《《给分割线腾出地方
        for(auto &k:f[now]) f[nxt][(k.first << 2) & U] += k.second;
        std::swap(now, nxt);
    }
    return 0;
}
int main() {
    init();
    printf("%lld\n", work());
    return 0;
}

转载于:https://www.cnblogs.com/olinr/p/10235519.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值