【Acwing291】蒙德里安的梦想(状态压缩dp)详细讲解

news2024/11/20 23:18:53

题目描述

题目分析

显而易见的重要事实

首先,需要明白一个很重要的事实:

所有的摆放方案数=所有横着摆放且合理的方案数

这是因为,横着的确定之后,竖着的一定会被唯一确定,举一个例子:

------唯一确定----->

所以使用动态规划进行状态表示的时候,仅仅需要考虑横着的长方形即可

状态表示

随后,我们来看状态表示:

f[i,j]表示:前i-1列已经摆好,且从第i-1列伸出到第i列的状态为j的所有方案数

注意:列数的下标是0~m-1

举一个例子:

 如上图,假设一个6*6棋盘,前两列已经以如图方式摆好,此时对于第三列的第一、四行有被第二列伸过来的部分,所以此时第二列的状态就是100100,将这个字符串看成二进制数,转为十进制为36,所以这就是f[2,36]所指的“所有方案数”的其中一个。

状态计算

对于同一列i,即使j值(状态数)相同,也会因为前i-1列的摆放方案不同而导致整体的摆放方案不同,也就是说,在前i-1列已经确定的情况之下,第i-1列的每一个状态的每一个方案都能够组合一个第i列的确定的状态j得到一个新的摆放方案:

比如上述这个图,列3的状态为:101010,由于列2可能存在不同的状态(0x0y0z),导致整体的摆放方案不同,同理,列2的某一种状态的方案数也因为列1可能存在不同的状态导致此方案数有可能大于1,所以呈现一个递归的现象,由此可得状态计算:

f[i,j]=\sum f[i-1,k]  

其中k为第i-1列的状态值,可以取不同的值

何时合理

显然,对于上述状态计算式子,如果不限制k值和j的关系,而一味地让k和j取遍整个0~2^n-1,就会发生一些不合理的现象,比如:

假设现在已经处理到i=3的情况,并且假设i=0~i=2的所有长方形已经摆好,就会发现两个不合理的现象:

1.在第一行的列2和列3地方发生了交叉

2.第二行的列2位置有一个不合理空缺,这个位置无法再填补任何的长方形,因为现在已经假定0~2列是摆好的,不会再去这个位置填补空缺的横着放的长方形,同时这个地方也根本不可能去放置竖长方形。

为什么产生了这个现象?

因为没有约束k和j的关系

我们先来看第一个不合理之处产生的原因:(k&j)!=0

顺便注意一下这里,k&j一定要加括号,因为!=的优先级大于&

因为如果k&j!=0,那么至少存在一行使得i和i-1列中同时有从i-1和i-2列伸过来的长方形,从而导致长方形的重叠

第二个不合理之处的产生原因:i-1列中的连续的空缺位置中存在空缺大小为奇数的情况

如何避免这个不合理之处呢?

保证:st[j|k]==true,其中st[x]为true的条件是x的二进制表示中没有奇数个连续的0

边界问题

开头已经说过了,此题列的表示下标从0开始,所以初始化为f[0][0]=1,意思是“没有从左边的棋盘边界的外边伸向第一列的长方形的方案数为1”,其它f全部初始化为0。

如果列数为m,列号从0开始,在遍历i的时候,最后遍历到的下标是m而不是m-1,因为当遍历到第i列的时候,实际上保证的是第i-1列的合法性,比如,刚才那个例子:

当遍历到i=3的时候,发现的两个不合理之处全在i=2列。

最后的答案为:f[m][0],意思是“在0~m-1列全部摆好的情况之下,棋盘右边界的右边一列没有第m列伸过来的长方形的总方案数”

朴素代码以及短路现象

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 12, M = 1 << N;

int n, m;
long long f[N][M];
bool st[M];

int main()
{
    while (cin >> n >> m, n || m)
    {
        for (int i = 0; i < 1 << n; i ++ )
        {
            int cnt = 0;
            st[i] = true;
            for (int j = 0; j < n; j ++ )
                if (i >> j & 1)
                {
                    if (cnt & 1) st[i] = false;
                    cnt = 0;
                }
                else cnt ++ ;
            if (cnt & 1) st[i] = false;
        }

        memset(f, 0, sizeof f);
        f[0][0] = 1;
        for (int i = 1; i <= m; i ++ )
            for (int j = 0; j < 1 << n; j ++ )
                for (int k = 0; k < 1 << n; k ++ )
                    if ((j & k) == 0 && st[j | k])
                        f[i][j] += f[i - 1][k];

        cout << f[m][0] << endl;
    }
    return 0;
}

在上y总的课时,y总说这个代码有一个神奇的现象,就是把(j & k) == 0 && st[j | k]中&&前后的部分进行交换之后,时间会变长许多,此代码从892ms变为1442ms,很显然这与&&的短路性有关,当

(j & k) == 0不满足时,就会跳过st[j | k]的判断,因为(j & k) == 0的“成功率”小于“st[j | k]”的成功率,所以会出现上述现象。

优化之后的代码

此代码已经经过优化:预处理去除无效状态

#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int N=12,M=1<<N;
typedef long long LL;
LL f[N][M];
bool st[M];
vector<int>state[M];//存放j和k的合法映射的集合
int n,m;
int main()
{
    while(scanf("%d%d",&n,&m)==2&&(n||m))
    {
        for(int i=0;i<1<<n;i++)//st数组的预处理
        {
            int cnt=0;
            st[i]=true;
            for(int j=0;j<n;j++)
            {
                if(i>>j&1)
                {
                    if(cnt&1)
                    {
                        st[i]=false;
                        break;
                    }
                    cnt=0;
                }
                else 
                cnt++;
            }
            if(cnt&1)st[i]=false;
        }
        for(int j=0;j<1<<n;j++)
        {
            state[j].clear();//为什么要清空?因为是多样例输入
            for(int k=0;k<1<<n;k++)
            if((k&j)==0&&st[k|j])
            state[j].push_back(k);//满足推出f[i][j]的第i-1行的其中一个状态数k
        }
        memset(f,0,sizeof f);//因为是多样例输入
        f[0][0]=1;
        for(int i=1;i<=m;i++)
        {
            for(int j=0;j<1<<n;j++)
            for(auto k:state[j])//代码优化的体现,大大减少了冗余
            f[i][j]+=f[i-1][k];
        }
        cout<<f[m][0]<<endl;
    }
    return 0;
}

 

 

 

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

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

相关文章

自动化测试定位不到元素?可能是 frame 在搞鬼

很多人在用Splinter或Selenium定位页面元素的时候会遇到定位不到的问题&#xff0c;明明元素就在那儿&#xff0c;就是定位不到&#xff0c;这种情况很有可能是frame在搞鬼。 说白了就是网站上的网页A&#xff0c;又嵌入了其他网页B。你访问了网页A&#xff0c;在里面可以看到…

4G智慧电力物联网:建设高效智能,引领电力行业革新!

随着4G与物联网技术的快速发展为电力行业提供了更高效、可靠、智能化的解决方案。本文中智联物联将为大家分享智慧电力系统中的一些关键的物联网技术和通讯设备&#xff0c;如工业4G路由器、分布式发电站、数据采集传输、远程监控管理以及变电站监测。 光伏发电站是电力行业中重…

Golang struct 结构体注意事项和使用细节

结构体所有字段在内存当中是连续的 type Point struct {x, y int }type Rect struct {leftUp, rightDown Point }func main() {//r1会在内存当中有四个整数r1 : Rect{leftUp: Point{x: 1,y: 2,},rightDown: Point{x: 3,y: 4,},}//r1有四个int&#xff0c;在内存当中是连续分布的…

Vue2-快速搭建pc端后台管理系统

一.推荐二次开发框架 vue-element-admin Star(84k)vue-antd-admin Star(3.5k) 二.vue-element-admin 官网链接:https://panjiachen.github.io/vue-element-admin-site/zh/ 我这里搭建的是基础模版vue-admin-template(推荐) # 克隆项目 git clone https://github.com/PanJi…

使用多种工具进行JVM调优、线上故障排查的例子

1 FullGC调优 面试官&#xff1a;如何进行 JVM 调优&#xff08;附真实案例&#xff09; 2 使用arthas诊断案例 2.1 使用arthas确定某一个耗时的请求来自哪一个controller&#xff0c;并且分析以及代码优化 2.1.1 为什么要做第一步的“确定请求来源的controller”&#xff…

切换Debian的crontab的nano编辑器

Debian的crontab默认的编辑器是nano&#xff0c;用起来很不习惯,怎么才能转回vim呢? 用以下命令便可&#xff1a; #update-alternatives --config editor 出现以下所示的界面&#xff1a; 而后选择8使用/usr/bin/vim就能够了。 PS&#xff1a;若是你发现你的定时没有生效&…

全新土地销售活动 Turkishverse——在数字十字路口占据一席之地

准备好与来自该地区的众多世界知名合作伙伴一起探索土耳其文化和历史吧&#xff01; 简单介绍 ● 在这个弘扬土耳其文化和历史的新社区中&#xff0c;共有 433 块 LAND 可供出售&#xff0c;其中包括 □ 380 块标准 LAND □ 48 块优质 LAND □ 5 个 Estate ● LAND 销售抽…

通达信唐奇安通道指标公式,海龟交易法则的先驱

唐奇安通道&#xff08;Donchian Channel&#xff09;是由Richard Donchian发明的技术分析指标&#xff0c;用于确定价格的趋势和波动。著名的海龟交易法则就是基于唐奇安通道设计的&#xff0c;将通道作为交易系统的一部分&#xff0c;用于捕捉趋势信号。唐奇安通道由三条线组…

【腾讯云Cloud Studio实战训练营】React 快速构建点餐页面+Python 拼图小游戏

文章目录 一、腾讯云 Cloud Studio 概述1.1 腾讯云 Cloud Studio 简介1.2 腾讯云 Cloud Studio 功能特点1.3 腾讯云 Cloud Studio 产品优势 二、Cloud Studio界面功能介绍2.1 注册登录2.1.1 新注册用户有免费的3000分钟体验 2.2 界面功能介绍2.2.1 空间模板2.2.2 开发空间关闭空…

二甲医院信息管理系统源码 his系统源码 java+Angular+JavaScript

云HIS系统采用SaaS软件应用服务模式&#xff0c;提供软件应用服务多租户机制&#xff0c;实现一中心部署多机构使用。主要包含收费计费、药品管理、门诊医生工作站、住院医生工作站、护士工作站、数据统计、电子病历、医保接口等功能&#xff0c;能够满足医院及诊所日常业务开展…

VR智慧课堂 | 临床兽医学VR实验教学有哪些好处?

随着科技的不断发展&#xff0c;虚拟现实(VR)技术已经逐渐渗透到各个领域&#xff0c;为人们带来了前所未有的体验。在动物医学实验教学中&#xff0c;VR技术的应用也日益受到关注。本文将探讨临床兽医学VR实验教学的好处。 首先&#xff0c;VR技术能够提高动物医学实验的安全性…

常用数据库备份方法,sql数据库备份方法

在信息时代&#xff0c;数据成为了公司的主要资产。然而&#xff0c;数据的安全性和完整性也成为企业管理的重要组成部分。因此&#xff0c;数据库备份至关重要。本文将详细介绍几种常见的数据库备份方法。 全备份 全备份是指数据库中所有数据的备份&#xff0c;包括数据文件、…

为什么要使用依赖注入?直接new对象不香吗?为什么要把简单的问题复杂化?

作者&#xff1a;newki 为什么要使用依赖注入&#xff1f;直接new对象不香吗&#xff1f;为什么要把简单的问题复杂化&#xff1f; 你是不是在炫技,是不是像装13&#xff1f; 这还真不是&#xff0c;如果说我使用的Dagger2&#xff0c;还真是炫技&#xff0c;NB啊。Dagger的坑…

首轮征稿 | 2024年第二届先进无人飞行系统国际会议(ICAUAS 2024)

会议简介 Brief Introduction 2024年第二届先进无人飞行系统国际会议(ICAUAS 2024) 会议时间&#xff1a;2024年4月5日-7日 召开地点&#xff1a;中国武汉 大会官网&#xff1a;ICAUAS 2024-2024 2nd International Conference on Advanced Unmanned Aerial Systems 由华中科技…

Postgres数据库,使用序列时,没有按自增序列,而且数值很大

文章目录 前言处理前现象原因如何配置序列搞定&#xff0c;再见 前言 最近写了个全局获取操作日志的注解&#xff0c;写在了一个公共模块。但是奇怪的是在有的服务可以自增&#xff0c;而有的不可以。这回中间的过程咱就不过多描述了。 处理前现象 会生成这种id贼大的 原因…

怎么入门网络安全(黑客)?

目录&#xff1a; 一、自学网络安全学习的误区和陷阱 1.不要试图先成为一名程序员&#xff08;以编程为基础的学习&#xff09;再开始学习2.不要把深度学习作为入门第一课3.以黑客技能、兴趣为方向的自学误区&#xff1a;4.不要收集过多的资料二、学习网络安全的一些前期准备三…

微信开发之一键邀请好友加入群聊的技术实现

邀请群成员&#xff08;开启群验证&#xff09; 若群开启邀请确认&#xff0c;仅能通过本接口邀请群成员 请求URL&#xff1a; http://域名/addChatRoomMemberVerify 请求方式&#xff1a; POST 请求头Headers&#xff1a; Content-Type&#xff1a;application/jsonAuth…

抖音店铺怎么运营?一定要牢记这几个运营细节,能让你少走弯路!

我是王路飞。 新手做抖音小店&#xff0c;一直没做起来不出单的原因&#xff0c;绝大多数都是因为不了解流程&#xff0c;不知道每一步该干什么&#xff0c;以及要做好哪些细节。 不管做什么项目&#xff0c;先入门都是新手的第一要务&#xff0c;只有入门、跑通了整个流程&a…

R包开发-2.2:在RStudio中使用Rcpp制作R-Package(更新于2023.8.23)

目录 4-添加C函数 5-编辑元数据 6-启用Roxygen&#xff0c;执行文档化。 7-单元测试 8-在自己的计算机上安装R包&#xff1a; 9-程序发布 参考&#xff1a; 为什么要写这篇文章的更新日期&#xff1f;因为R语言发展很快&#xff0c;很多函数或者方式&#xff0c;现在可以使…

上传WSL项目到gitlab

上传WSL项目到gitlab 设置ssh将SSH公钥添加到Gitlab 将WSL上的代码上传到gitlab确保在WSL环境中安装了git下面是上传代码到GitLab的具体步骤&#xff1a; 可能遇到的各种错误 设置ssh Gitlab添加SSH KEY 什么是SSH ? SSH 是一种网络协议&#xff0c;具备协议级别的认证及会话…