插头 DP

news2024/11/26 9:38:12

垃圾插头DP,照着打都调了我一下午,淦!!!

学这个玩意纯粹是因为模拟赛考了一道,要不然碰都不会碰……

我觉得插头DP的主要难度在于实现,而不是理解算法原理……

不说废话了,进入正题。

解决的问题

学一个新知识点,肯定先要了解这个知识点能够解决什么样的问题。(废话)

其实插头DP就是一类特殊的状压DP,他解决的就是在小数据规模下连通性的问题,数据太大插头DP也不好做,毕竟本质还是状压。

举几个简单的例子,就比如说格点图的哈密顿路径计数,求棋盘的黑白染色方案满足相同颜色之间形成一个连通块的方案数,以及特定图的生成树计数等等,这些你看起来连暴力都不是很好搞的东西,插头DP就可以做。(说实话,就这复杂度,说暴力都有人信

接下来我们先来了解几个概念。

基本概念


插头

如果一个格子某个方向有插头,表示这个格子在这个方向与相邻格子是相连的。

简单点来说就是记录当前格子与相邻的格子是否连通。

轮廓线

就是已决策格子与未决策格子的分界线。(下图红色的线)

在这里插入图片描述

注意一下,这里分界线的边数是列数加一,别搞错了。


大概就这两个概念,我们现在来看看插头DP如何实现

实现

我们就借一道板题来说。

Luogu P5056 【模板】插头DP

因为数据较小,但是搜索过不了,于是考虑状压。(即插头DP)

可以逐格DP(某些题目可以逐行),设 f i , j , s t a t e f_{i,j,state} fi,j,state 表示当前的方案数。

通常的 s t a t e state state 有括号表示和最小表示,这里着重介绍泛用性更好的最小表示。我们用长度为 m + 1 m+1 m+1 的整形数组,记录轮廓线上每个插头的状态, 0 0 0 表示没有插头,并约定连通的插头用相同的数字进行标记。

由于要形成最小回路,可知最多只有两个插头互相连通,并且跟两组连通的插头之间不会交叉,既不会出现 2   1   2   1 2\ 1\ 2\ 1 2 1 2 1 的情况,并且每一个格子最多只有两个插头。

考虑如何转移。

在这里插入图片描述

对于上图黄色的格子,轮廓线为红色, 蓝色使我们DP完当前节点后的轮廓线(包括第一、二、四列的红线),我们分以下几种情况考虑。

  • 跟上面和左边的格子都有插头,此时还要分两种情况。
    • 如果这两个插头没连通,则这个格子则不能有右插头和下插头,即新的两边轮廓线上没有任何插头。
    • 如果这两个插头连通了,若当前枚举的格子为最后一个格子,则直接连通,否则不能连通。(若连通则成为多个闭合回路了)
  • 跟上面和左边的格子有一个插头,则此时跟下面和右边任意一个格子也有一个插头。
  • 上面和左边都没有插头,则此时右边和下面都必须有插头。

注意考虑一下边界和障碍的条件。

当我们DP完一行时,轮廓线会变为下图形式:

在这里插入图片描述
此时我们在DP下一行之前记得将整个状态左移即可。

优化

对于普通的插头DP,时空复杂度也并不是非常优,此时就需要一点优化。

我们会发现由于他是闭合回路,它的状态会非常稀疏,于是我们可以用 hash 来优化。

但这个 hash 要用挂表法,即将你所有模数为 x x x 的状态都挂到一起,可以用链式前向星维护。

你每次遇到一个新状态就将它存到哈希里面,然后查找状态则将那一条 hash 链枚举一遍即可。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 200005, P = 9973, offset = 4, mask = (1 << offset) - 1;
int n, m, mp[20][20], ex, ey;
LL Val;
int in() {
    char ch = getchar();
    while (ch != '.' && ch != '*')
        ch = getchar();
    return ch == '*';
}
struct HashTable {//hash的实现
    int last[P], next[N], cnt;
    LL ans[N], state[N];
    void clear() {
        cnt = 0;
        memset(last, 0, sizeof(last));
    }
    void push(LL s) {
        LL x = s % P;
        for (int i = last[x]; i; i = next[i])
            if (state[i] == s) {
                ans[i] += Val;
                return ;
            }
        cnt++, state[cnt] = s, ans[cnt] = Val;
        next[cnt] = last[x], last[x] = cnt;
    }
    void roll() {
        for (int i = 1; i <= cnt; i++)
            state[i] = state[i] << offset;
    }
} H[2], *H0, *H1;
int bb[25], b[25];
LL encode() {//这是将数组加码成状态
    LL s = 0;
    memset(bb, -1, sizeof(bb));
    int bn = 1;
    bb[0] = 0;
    for (int i = m + 1; i; i--) {
        if (!~bb[b[i]])
            bb[b[i]] = bn++;
        s <<= offset;
        s |= bb[b[i]];
    }
    return s;
}
void decode(LL s) {//这是将状态解码成数组
    for (int i = 1; i <= m + 1; i++) {
        b[i] = s & mask;
        s >>= offset;
    }
}
void push(int j, int down, int right) {//将状态改变,然后往下DP
    b[j] = down, b[j + 1] = right;
    H1 -> push(encode());
}
void PlugDP() {//DP
    H0 = H, H1 = H + 1;
    H1 -> clear(), Val = 1, H1 -> push(0);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            swap(H0, H1), H1 -> clear();
            for (int l = 1; l <= (H0 -> cnt); l++) {
                decode(H0 -> state[l]);
                Val = H0 -> ans[l];
                int left = b[j], up = b[j + 1];
                //上面和左边的插头类型
                bool down = i != n, right = j != m;;
                //这两个变量即是判断是否处于边界位置,能否有右插头和下插头
                if (mp[i][j] == 1) {//如果当前格子为障碍
                    if (!left && !up) {//如果上面和左边都没有插头,则可以转移成下面和右边也都没有插头
                        push(j, 0, 0);
                    }
                }
                else if (left && up) {//上面和左边都有插头
                    if (left == up) {//两个插头连通
                        if (i == ex && j == ey)
                            push(j, 0, 0);
                    }
                    else {//两个插头不连通
                        for (int t = 1; t <= m + 1; t++)
                            if (b[t] == left)
                                b[t] = up;
                        push(j, 0, 0);
                    }
                }
                else if (left || up) {//上面和左边只有一个插头
                    int t = left | up;
                    if (down)
                        push(j, t, 0);
                    if (right)
                        push(j, 0, t);
                }
                else if (down && right)//两边都没有插头并且下面和右边都可以有插头
                    push(j, m, m);
            }
        }
        H1 -> roll();//即全部右移
    }
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            mp[i][j] = in();
            if (!mp[i][j])
                ex = i, ey = j;
        }
    PlugDP();
    cout << (H1 -> cnt == 1 ? H1 -> ans[1] : 0) << endl;
    return 0;
}

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

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

相关文章

2023腾讯云服务器优惠代金券领取、查询及使用说明

腾讯云代金券领取渠道有哪些&#xff1f;腾讯云官网可以领取、官方媒体账号可以领取代金券、完成任务可以领取代金券&#xff0c;大家也可以在腾讯云百科蹲守代金券&#xff0c;因为腾讯云代金券领取渠道比较分散&#xff0c;腾讯云百科txybk.com专注汇总优惠代金券领取页面&am…

Javascript 笔记:object

一部分object可以见&#xff1a;JavaScript 笔记 初识JavaScript&#xff08;变量&#xff09;_UQI-LIUWJ的博客-CSDN博客 1 in操作符 2 hasOwnProperty 3 获取一个object所拥有的所有property 不去原型链上找 4 定义data property

ARM day5

三盏灯流水 .text .global _start _start: 1.LDR R0,0X50000A28LDR R1,[R0]ORR R1,R1,#(0X1<<4)STR R1,[R0] 1.LDR R0,0X50000A28LDR R1,[R0]ORR R1,R1,#(0X1<<5)STR R1,[R0] 2.LDR R0,0X50006000LDR R1,[R0]BIC R1,R1,#(0X3<<20)ORR R1,R1,#(0X1<<…

如何快速制作令人惊叹的长图海报

在当今的数字时代&#xff0c;制作一张吸引人的长图海报已成为许多人的需求。无论是为了宣传活动&#xff0c;还是展示产品&#xff0c;一张设计精美的长图海报都能引起人们的注意。下面&#xff0c;我们将介绍一种简单的方法&#xff0c;使用在线海报制作工具来创建长图海报。…

IO 之 操作properties属性文件

propreties文件&#xff1a; properties文件是一种用于存储配置信息的文本文件&#xff0c;通常以“.properties”为文件扩展名。它是一种简单的键值对格式&#xff0c;用于保存应用程序的配置参数。 在properties文件中&#xff0c;每一行都包含一个键值对&#xff0c;键和值…

Legion Y9000X IRH8 2023款(82Y3)原装出厂OEM预装Windows11系统

lenovo联想电脑笔记本拯救者原厂win11系统镜像 下载链接&#xff1a;https://pan.baidu.com/s/15G01j7ROVqOFOETccQSKHg?pwdt1ju 系统自带所有驱动、出厂主题壁纸、Office办公软件、联想电脑管家等预装程序 所需要工具&#xff1a;32G或以上的U盘 文件格式&#xff1a;ISO…

【AI视野·今日CV 计算机视觉论文速览 第261期】Thu, 5 Oct 2023

AI视野今日CS.CV 计算机视觉论文速览 Thu, 5 Oct 2023 Totally 75 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computer Vision Papers Consistent-1-to-3: Consistent Image to 3D View Synthesis via Geometry-aware Diffusion Models Authors Jianglong Ye, …

Matlab参数估计与假设检验(举例解释)

参数估计分为点估计和区间估计&#xff0c;在matlab中可以调用namefit()函数来计算参数的极大似然估计值和置信区间。而数据分析中用得最多的是正态分布参数估计。 例1 从某厂生产的滚珠中抽取10个&#xff0c;测得滚珠的直径&#xff08;单位&#xff1a;mm&#xff09;为x[…

用好CompletableFuture类,性能起飞

目录 CompletableFuture引言 CompletableFuture本质 CompletableFuture与Future的关系 CompletableFuture创建 同步方法 异步方法 CompletableFuture执行结果 一元依赖 二元依赖 多元依赖 CompletableFuture异常处理 CompletableFuture实现原理 Java 8中引入了Comp…

【力扣-每日一题】2034. 股票价格波动

class StockPrice { private:unordered_map<int,int> mp; //存储日期及其对应的价格multiset<int> st; //存储所有价格int last_day; //最新一天 public:StockPrice() {this->last_day0;}void update(int timestamp, int price) {if(mp.find(timestamp)!mp…

Java——String与StringBuffer的区别

Java——String类与StringBuffer类的区别 1. ●String类表示的字符串是常量&#xff0c;一旦创建后其内容和长度是无法改变的。 ●StringBuffer表示字符容器&#xff0c;其内容和长度是可以修改的。 在操作字符串时&#xff0c;如果该字符串仅用于表示数据类型&#xff0c;则使…

[鹏城杯 2022]简单的php - 无数字字母RCE+取反【*】

[鹏城杯 2022]简单的php 一、解题流程二、思考总结 题目代码&#xff1a; <?php show_source(__FILE__);$code $_GET[code];if(strlen($code) > 80 or preg_match(/[A-Za-z0-9]|\|"||\ |,|\.|-|\||\/|\\|<|>|\$|\?|\^|&|\|/is,$code)){die( Hello);}e…

【juc】future并行执行并获取返回值

目录 一、截图示例二、代码示例2.1 接口示例2.2 调用示例 一、截图示例 二、代码示例 2.1 接口示例 package com.learning.controller;import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.…

基于Springboot实现社区维修平台管理系统演示【项目源码+论文说明】分享

基于Springboot的社区维修平台管理系统演示 摘要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&am…

Transformer预测 | Python实现基于Transformer的股票价格预测(tensorflow)

文章目录 效果一览文章概述程序设计参考资料效果一览 文章概述 Transformer预测 | Python实现基于Transformer的股票价格预测(tensorflow) 程序设计 import numpy as np import matplotlib.pyplot

C语言 - 数组

目录 1. 一维数组的创建和初始化 1.1 数组的创建 1.2 数组的初始化 1.3 一维数组的使用 1.4 一维数组在内存中的存储 2. 二维数组的创建和初始化 2.1 二维数组的创建 2.2 二维数组的初始化 2.3 二维数组的使用 2.4 二维数组在内存中的存储 3. 数组越界 4. 数组作为函数参数 4.1…

C++11 Thread线程库的使用

C11 Thread线程库的使用 传统的C&#xff08;C11标准之前&#xff09;中并没有引入线程这个概念&#xff0c;在C11出来之前&#xff0c;如果我们想要在C中实现多线程&#xff0c;需要借助操作系统平台提供的API&#xff0c;比如Linux的&#xff0c;或者windows下的 。 本文详细…

【数据结构】什么是算法

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 目录 一.算法的定义 1.算法的概念 2.数据结构与算法的关系 二.算法的特性 输入 输出 有穷性 确定性 可行性 三.算法的设计要求 1.正确性 2.可读性 3.健壮性 4.效…

DALL·E 3 ChatGPT-4的梦幻联动

核心内容&#xff1a;DALLE 3 & ChatGPT-4的梦幻联动 hello&#xff0c;我是小索奇&#xff0c;最近DALL结合ChatGPT4的话题逐渐上升了起来&#xff0c;今天就带大家探索一下~ DALLE的主要功能是根据文本描述来生成图片。你可以告诉它一个穿着皮草的西瓜&#xff0c;它就能…

叠氮修饰的葡萄糖Ac4GIcNAz,98924-81-3

产品简介&#xff1a;The tetraacetylatedN-Azidoacetyl-glucosamine (Ac4GlcNAz) provides a non-radioactive alternativefor glycoconjugate visualization. It is cell-permeable, intracellularlyprocessed and incorporated instead of its natural monosaccharide count…