DP(9)--插头DP

news2024/11/20 1:54:32

DP(9)--插头DP

/*
    Mondriaan’s Dream题目大意:在 N*M 的棋盘内铺满 1*2 或 2*1 的多米诺骨牌,求方案数。
    
    砖只有横放和竖放两种状态,把横放记为两个0,竖放记为上1下0,逐格DP,每次无论前一格放怎么放,
    当前格可竖放或不放,而如果前一格是1,且当前格是0,那么我们可以把前一格改成0,再把当前格也放上0组成一块。
    这样成对合并和生成插头。
*/

#include <iostream>
#include <string.h>
using namespace std;

const int W = 11;
long long f[2][1 << W];

// 将a的第b位取反,最低位编号为0
int flapBit(int a, int b) 

    return a ^ (1 << b);
}

// O(h*w*2^w), 交换为了得到窄列,降低时间复杂度 
long long calc(int h, int w)
{
    if (h < w)
        swap(h, w);

    memset(f, 0, sizeof(f));
    int cur = 0;
    f[cur][0] = 1;
    for (int i = 0; i < h; ++i)
        for (int j = 0; j < w; ++j)
        {
            cur ^= 1;
            memset(f[cur], 0, sizeof(f[cur]));
            for (int k = 0; k < (1 << w); ++k) // 枚举状态
            {
                if (f[cur^1][k] > 0)
                {
                    f[cur][flapBit(k, j)] += f[cur^1][k];  // 竖放或不放
                    if (j != w - 1 && (!((k >> j) & 3))) // 非最后一列且满足能容纳2个单位的宽度
                        f[cur][flapBit(k, j+1)] += f[cur^1][k];  // 横放
                }
            }
        }

    return f[cur][0];
}

int main()
{
    int h, w;
    while (cin >> h >> w && h != 0 && w != 0)
        cout << calc(h, w) << endl;

    return 0;
}

一个方向的插头存在表示这个格子在这个方向可以与外面相连。
对于一个4连通的问题来说,它通常有上下左右4个插头。

插头的连通性(有没有下插头,与轮廓线直接相连的插头)

砖只有横放和竖放两种状态,把横放记为两个0,竖放记为上1下0,逐格DP,每次无论前一格放怎么放,当前格可竖放或不放,
而如果前一格是1,且当前格是0,那么我们可以把前一格改成0,再把当前格也放上0组成一块。

dp[i][j][state] 为位置(i,j),状态为state的方案数

插头存在信息 格子连通信息

优化为位置(i,j)插头的连通状态为S的方案数: f(i, j, S)
f(3, 1, {1, 0, 1, 2, 2}), f(3, 2, {1, 0, 1, 2, 2}), f(3, 3, {1, 0, 0, 0, 1})


情况1 新建一个连通分量,这种情况出现在(i, j)有右插头和下插头。
新建的两个插头连通且不与其他插头连通,这种情况下需要将这两个插头
连通分量标号标记为一个未标记的正数,重新O(n)扫描保证新的状态满足最小表示。
情况2 合并两个连通分量,这种情况出现在(i, j)有上插头和左插头。
如果两个插头不连通,那么将两个插头所处的连通分量合并,标记相同的连通块标号,
O(n)扫描保证最小表示;如果已经连通,相当于出现一个回路,这种情况只能出现在最后一个非障碍格子。
情况3 保持原来的连通分量,这种情况出现在(i, j)的上插头和右插头恰好有一个或下插头和左插头也恰好有一个。
下插头或右插头相当于上插头或左插头的延续,连通块标号相同,并且不会影响到其他的插头的连通块标号,
计算新的状态的时间为O(1).

注意当从一行的最后一个格子转移到下一行的第一个格子的时候,轮廓线需要特殊处理。
值得一提的是,上面三种情况计算新的状态的时间分别为O(n), O(n), O(1), 如果使用前面提到的第二种最小表示法,
情况1只需要O(1),但是情况3可能需要O(n)重新扫描。

对n+1个元素进行编码,将其表示成一个n+1位的p进制数,p可以取能够达到的最大的连通块标号加1,
对本题来说,最多出现n/2<=6个连通块,不妨取p=7,在不超过数据类型范围的前提下,建议将p改为2的幂,
因为位运算比较快,本题最好采用8进制来存储。
如需大范围修改连通块标号,最好将状态O(n)解码到一个数组中,修改后再O(n)计算出新的p进制数,
而对于只需要局部修改几个标号的情况下,可以直接用(x/p^(i-1))%p来获取第i位,用加或者减k*p^(i-1)
直接对第i位进行修改。


唯一特殊的是上一行末到这一行头的处理。上一行末不可能有右插头,那我们直接把上一行末状态的表示最后是否存在右插头的位置去掉,
再添加一个表示没有左插头的位,就表示出了下一行行首的状态,为了方便写,在代码里,用dp[i][0][mask]表示转移后的上一行行末状态。

//https://www.luogu.com.cn/problem/P5056

#include <iostream>
#include <string.h>
using namespace std;
const int M = 15;
const int offset = 3, mask = (1 << offset) - 1;
int n, m;
long long ans;
//MaxSZ: 合法状态的上界,可以估计,也可以预处理出较为精确的值。
//Prime: 一个小于 MaxSZ 的大素数
const int MaxSZ = 16796, Prime = 9973;
bool path[M][M];

struct hashTable 
{
/*
    head[] 表头节点的指针。
    next[] 后续状态的指针。
    state[] 节点的状态。
    key[] 节点的关键字,在本题中是方案数。
*/
    int head[Prime], next[MaxSZ], sz;
    long long state[MaxSZ];
    long long key[MaxSZ];

/* 初始化函数,和手写邻接表类似,我们只需要初始化表头节点的指针 */
    inline void init() 
    {
        sz = 0;
        memset(head, -1, sizeof(head));
    }
/*  
    状态转移函数,其中 d 表示每次状态s转移所带来的增量。
    如果找到的话就 +=,否则就创建一个状态为 s,关键字为 d 的新节点
*/
    inline void push(long long s, long long d) 
    {
        int x = s % Prime;
        for (int i = head[x]; ~i; i = next[i])
        {
            if (state[i] == s) // s状态存在,直接更新key[]
            {
                key[i] += d;
                return;
            }
        }
        // 添加新状态
        state[sz] = s;
        key[sz] = d;
        next[sz] = head[x];
        head[x] = sz++;
    }
}H[2];

/*
    code[]: 轮廓线上的插头的状态编码
    arr[]: 最小表示法的编码过程中,每个数字被映射到的最小数字。0表示插头不存在,不能被映射到其他值。
*/
int code[M + 1], arr[M + 1];

/*
    最小表示法 m<=12 最多只有6个不同的连通分量,
    对m+1个元素进行编码,将其表示成一个m+1位的p进制数,p可以取能够达到的最大的连通块标号加1,
    本题最好采用8进制来存储, 将插头连通状态数组code进行8进制压缩,转为8进制数s。
*/
long long encode() 
{
    long long s = 0;
    memset(arr, -1, sizeof(arr));
    // 最小表示法,连通块编号从1开始
    int bn = 1;
    arr[0] = 0;
    for (int i = 0; i <= m; ++i)
    {
        if (!~arr[code[i]]) // arr[] 为 -1, 即出现一个新的连通分量,添加新编号
            arr[code[i]] = bn++;
        s <<= offset;  // 逐位进行8进制压缩
        s |= arr[code[i]];
    }

    return s;
}

// 将8进制压缩码解析到code数组
void decode(long long s)
{
    for (int i = m; i >= 0; --i)
    {
        code[i] = s & mask;
        s >>= offset;
    }
}

void push(int cur, int j, int dn, int rt, long long d) 
{
    code[j-1] = dn;
    code[j] = rt;
    H[cur].push(encode(), d);
}

int main() 
{
    cin >> n >> m;
    char str[32] =  { '\0' };
    int row = 0, colum = 0;
    for (int i = 1; i <= n; ++i)
    {
        cin >> str+1;
        for (int j = 0; j <= m; ++j)
            if (str[j] == '.')
            {
                path[i][j] = true;
                row = i;
                colum = j;
            }
            else
                path[i][j] = false;
    }

    if (!row)
    {
        cout << 0 << endl;
        return 0;
    }

    int cur= 0;
    H[cur].init();
    long long d = 1; // 初始状态0的增量delta为1
    H[cur].push(0, d);
    for (int i = 1; i <= n; ++i) 
    {
        for (int j = 1; j <= m; ++j) 
        {
            if (path[i][j])
            {
                cur ^= 1;
                H[cur].init();
                for (int s = 0; s < H[cur^1].sz; ++s) 
                {
                    decode(H[cur^1].state[s]);  // 取出状态,并解码
                    d =  H[cur^1].key[s];        // 得到增量 delta
                    int lt = code[j-1], up = code[j]; // 左插头,上插头

                    if (lt && up) // 如果左、上均有插头
                    {
                        if (lt == up) // 来自同一个连通块
                        { 
                            if (i == row && j == colum)  // 只有在最后一个格子时,才能合并,封闭回路。
                                push(cur, j, 0, 0, d);
                        } 
                        else  // 否则,必须合并这两个连通块,因为本题中需要回路覆盖
                        {
                            for (int k = 0; k <= m; ++k)
                                if (code[k] == lt) 
                                    code[k] = up;
                            push(cur, j, 0, 0, d);
                        }
                    } 
                    else if (lt || up)  // 如果左、上之中有一个插头
                    {
                        int t = lt | up;// 得到这个插头
                        if (path[i+1][j]) // 如果可以向下延伸
                            push(cur, j, t, 0, d);
                        if (path[i][j+1]) // 如果可以向右延伸
                            push(cur, j, 0, t, d);
                    }
                    else // 如果左、上均没有插头
                    {
                        if (path[i+1][j] && path[i][j+1])  // 生成一对新插头
                            push(cur, j, 7, 7, d);         // 插头连通分量最大值不超过7
                    }
                }
            }
        }
        /*  迭代完一整行之后,滚动轮廓线 */
        for (int j =0; j < H[cur].sz; ++j)
            H[cur].state[j] >>= offset;

    }
    

    cout << (H[cur].sz > 0 ? H[cur].key[0] : 0) << endl;

    return 0;
}

/*
测试数据
4 4
**..
....
....
....
2


4 4
....
....
....
....
6

12 12
..**********
...*********
....********
*....*******
**....******
***....*****
****....****
*****....***
******....**
*******....*
********....
*********...
1


*/
参考:

https://oi-wiki.org/dp/plug/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/495689.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Kali Linux 配置动态/静态 IP

[笔者系统版本] [Kali]: Kali Linux 2023.1 [Kernel]: kernel 6.1.0 [Desktop]: Xfce 4.18.1 1. Kali Linux 配置动态 IP (1). 首先查看网卡接口名称。 (2). 编辑网络接口配置文件。 (3). 网络接口配置文件的默认内容是这样的。 (4). 新增配置内容如下&#xff1b; 指定网卡…

ChatGPT :十几个国内免费可用 ChatGPT 网页版

前言 ChatGPT&#xff08;全名&#xff1a;Chat Generative Pre-trained Transformer&#xff09;&#xff0c;美国OpenAI 研发的聊天机器人程序 &#xff0c;于2022年11月30日发布 。ChatGPT是人工智能技术驱动的自然语言处理工具&#xff0c;它能够通过理解和学习人类的语言…

浅析智慧充电桩云平台的技术设计方案

自从我国提出“新基建”以来&#xff0c;充电基础设施产业也成为行业的话题与关注焦点。据数据统计&#xff0c;2021年&#xff0c;中国新能源汽车保有量达到784万辆&#xff0c;预计2025年&#xff0c;中国新能源汽车保有量达到2672万辆&#xff0c;2025年充电桩数量将达到654…

SwiftUI 新 Alert 弹出窗口帮你实现文本输入之梦

概览 小伙伴们都知道&#xff0c;弹出 Alert 不能包含文本输入框是 SwiftUI 的阿喀琉斯之踵(Achilles’ Heel) 。当然&#xff0c;这说的有些夸张了。&#x1f609; 不过&#xff0c;Alert 不能包含 TextField 在某些情况下着实不方便。于是乎&#xff0c;从 SwiftUI 3.0&…

[pgrx开发postgresql数据库扩展]附1.存储过程的优缺点与数据库扩展函数

俗话说&#xff1a;天下大势&#xff0c;分久必合&#xff0c;合久必分。 最早的软件系统开发&#xff0c;讲究的就是一个全栈——在最早期的桌面软件时代&#xff0c;数据、用户界面和业务逻辑是完全混在一起的&#xff0c;讲究的就是一个一体化……那个年代也诞生了大量的码农…

MySQL基础(三)基本的SELECT语句

1. SQL概述 1.1 SQL背景知识 1946 年&#xff0c;世界上第一台电脑诞生&#xff0c;如今&#xff0c;借由这台电脑发展起来的互联网已经自成江湖。在这几十年里&#xff0c;无数的技术、产业在这片江湖里沉浮&#xff0c;有的方兴未艾&#xff0c;有的已经几幕兴衰。但在这片浩…

同步辐射散射数据处理:从测量到分析的全流程解析

同步辐射散射数据处理&#xff1a;从测量到分析的全流程解析 同步辐射&#xff08;Synchrotron radiation&#xff0c;SR&#xff09;是指粒子在强磁场中受到加速或转向时所放出的辐射。这种辐射是一种非常强烈、具有非常高能量和亮度的电磁辐射。同步辐射散射&#xff08;Sync…

怎么控制别人的电脑屏幕?

为什么需要控制别人的屏幕&#xff1f; 我们不可避免地会遇到一些情况&#xff0c;比如我们需要为我们的朋友、同事或家人提供有关 IT 相关问题的帮助&#xff0c;如果他们不知道它该怎么处理这些问题该怎么办呢&#xff1f; 这时&#xff0c;我们可能需要用我们的电脑…

聊点技术 | 全新功能,让Bonree ONE变得更强

4月21日&#xff0c;博睿数据ONE有引力2023春季产品发布会圆满落幕&#xff0c;Bonree ONE 2023春季正式版正式发布&#xff0c;带来更轻、更强、更智能的一体化智能可观测平台。 全新功能&#xff0c;让Bonree ONE变得更强 本文作者 产品经理高天明、产品经理吴学飞、产品经…

Mysql监控账号创建【Prometheus】

Mysql的监控采用一个使用mysqld_exporter启动多个进程来实现监控多个mysql实例。 代理部署架构如下 1&#xff0c;创建数据库监控账号 create user 监控账号mysqld_exporter主机的IP identified by 监控密码 with max_user_connections 10; 2&#xff0c;权限授予 grant pr…

KDSL-82-1000A大电流发生器

一、产品简介 KDSL-82-1000A大电流发生器&#xff08;简称升流器&#xff09;&#xff0c;我公司自行研制开发的测试设备&#xff0c;它集国内外同类产品的优点于一身&#xff0c;采用数控技术&#xff0c;抗干扰能力强&#xff0c;和上一代升流器相比&#xff0c;由于采用低功…

NetSuite .id的用法

我们必须认清一个事实&#xff0c;NetSuite Saved Search是一个被封装化的SQL查询工具。在NetSuite的早期版本中&#xff0c;可以利用Formula字段做很多SQL语句上的灰色应用。但是慢慢的&#xff0c;灰色应用范围被压缩了。目前只剩下一个“.id”的应用了。 今朝我们就谈谈.id…

网络管理员优化提高网络性能需要关注的三个指标

网络管理员有一个主要责任&#xff1a;确保其网络的正常运行时间&#xff0c;同时不影响网络性能。然而&#xff0c;随着现代可部署解决方案通过传统的网络架构&#xff0c;这说起来容易做起来难。尽管现代解决方案在效率方面绝对比传统解决方案更上一层楼&#xff0c;但它们也…

任务态相关的一些知识总结

静息态的数据处理现在都比较简单了&#xff0c;因为有了fmriprep和qsiprep流程工具&#xff0c;没有特别的难度。 而关于任务态&#xff0c;则有一些独特的处理方式&#xff0c;因为最近要做任务态的数据分析&#xff0c;所以学习一下。 先学习下R的4个重输出函数&#xff1a;…

更懂业务的数智平台,应对数智化转型的“千变万化”

本文作者 跨界的申斯基 毫无疑问&#xff0c;随着数智化转型的加速&#xff0c;越来越多的企业正在把数智化战略提升到一个全新的高度&#xff0c;转型的进程也正从“浅层次”的数智化走向“深层次”数智化的阶段。 这也让企业的数智化转型进入到了一个全新的阶段&#xff0c;…

windows权限维持之shift后门

原理&#xff1a;沾滞键的目的是为了帮助那些按键有困难的人设计的&#xff0c;在Windows系统下连续按5次shift键后&#xff0c;系统就会执行C:\Windows\System32下的sethc.exe&#xff0c;也就是启用了沾滞键&#xff0c;但是当我们将shell.exe文件把sethc.exe文件通过更改名称…

AI 工具合辑盘点(七)持续更新 之 AI 音乐制作工具

AI 音乐制作工具 AI 也正在民主化音乐。它正在改变音乐创作的方式&#xff0c;赋予每个人成为音乐家的机会。创作音乐杰作所需的一切只是一个想法、一台电脑和一个 AI 音乐生成器。&#x1f3b6; AI 生成的音乐可以在几分钟内创作出来&#xff0c;结果取决于你的提示——几乎…

Python-web开发学习笔记(1)--- HTML基础

大家好&#xff0c;我是尚拙谨言&#xff0c;欢迎来到本专栏。本专栏主要是为web开发学习服务的&#xff0c;一共分为3个部分&#xff1a;前端介绍、数据库、Django&#xff0c;均为python web开发的学习笔记&#xff0c;希望能帮助到大家&#xff0c;也以此作为我个人的学习记…

Qt 从入门到入土【下篇】

Qt 从入门到入土【上篇】 推荐一个非常好的视频 本文目录 6. 对话框QDialog6.1 基本概念6.2 标准对话框6.3 自定义消息框6.4 消息对话框6.5 标准文件对话框 7. 布局管理器7.1 系统提供的布局控件7.2 利用widget做布局 8. 常用控件8.1 QLabel 控件使用8.2 QLineEdit8.3 其他控件…

从不同视角来看待API数据接口

一、作为产品经理向客户推广API数据接口&#xff0c;需要注意以下几点&#xff1a; 1.了解目标客户&#xff1a;在推广API数据接口之前&#xff0c;首先需要了解目标客户的需求和痛点&#xff0c;针对客户的实际情况提供更具体化的解决方案。 2.强调API接口的价值&#xff1a…