0x56 动态规划-状态压缩DP

Mondriaan's Dream

Description

Squares and rectangles fascinated the famous Dutch painter Piet Mondriaan. One night, after producing the drawings in his ‘toilet series’ (where he had to use his toilet paper to draw on, for all of his paper was filled with squares and rectangles), he dreamt of filling a large rectangle with small rectangles of width 2 and height 1 in varying ways.

Expert as he was in this material, he saw at a glance that he’ll need a computer to calculate the number of ways to fill the large rectangle whose dimensions were integer values, as well. Help him, so that his dream won’t turn into a nightmare!

Input

The input contains several test cases. Each test case is made up of two integer numbers: the height h and the width w of the large rectangle. Input is terminated by h=w=0. Otherwise, 1<=h,w<=11.

Output

For each test case, output the number of different ways the given rectangle can be filled with small rectangles of size 2 times 1. Assume the given large rectangle is oriented, i.e. count symmetrical tilings multiple times.

Sample Input

1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0
Null

Sample Output

1
0
1
2
3
5
144
51205
Null

题意

给出一个 n×m 的方格,问用 1×2 的小方格来填充总共有多少种方法。

思路

我们定义 dp[i][j] 代表第 i-1 行已经放满,第 i 行状态为 j 时候的方案数。

其中每一行的状态我们可以用一个二进制来表示, 0 代表未填充, 1 代表已填充。

因为方块有两种摆放形式:竖放、横放

所以当第 i 行第 j 列竖放一个方块时,第 i-1 行第 j 列需要留空;而当第 i 行第 j 列与第 j+1 列横放一个方块时,第 i-1 行第 j 列与第 j+1 列则需已填充,因为我们定义的 dp 需要把第 i-1 行全部放满。

状态转移方程: \(dp[i][s]=sum(dp[i−1][si])dp[i][s]=sum(dp[i−1][si])\) 其中状态 s 与状态 si 必须兼容,也就是状态 s 中竖放的块能够填满状态 si 中的空。

这里有一个优化,也就是当 \(n×m\)结果为奇数的时候,无论怎样都不可能成功放置,因为每一个块的面积是偶数。

// https://www.dreamwings.cn/poj2411/4615.html 千千dalao的解法
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std; #define MAX ((1<<11)+10)
typedef __int64 LL;
int n,m; LL dp[15][MAX];
LL ans[15][15]; bool jud(int x) // 判断 x 二进制中是否存在独立的1
{
bool is1=false;
for(int i=0; i<m; i++)
{
if(x&(1<<i))
is1=!is1;
else if(is1)return false;
}
return true;
} bool ok(int s, int ss) //判断状态s与状态ss是否兼容
{
for(int j=0; j<m; )
if(s & (1<<j)) //第i行第j列为1
{
if( ss & (1<<j)) //第i-1行第j列也为1,那么第i行必然是横放
{
//第i行和第i-1行的第j+1都必须是1,否则是非法的
if( j==m-1 || !(s&1<<(j+1)) || !(ss&(1<<(j+1))) ) return false;
else j+=2;
}
else j++; //第i-1行第j列为0,说明第i行第j列是竖放
}
else //第i行第j列为0,那么第i-1行的第j列应该是已经填充了的
{
if(ss&(1<<j)) j++;//已经填充
else return false;
}
return true;
} void solve()
{
int maxs;
if(n<m)swap(n,m); // 交换之后可以得到更小的状态数
maxs=(1<<m)-1; // 状态全1的情况
memset(dp,0,sizeof(dp));
for(int i=0; i<=maxs; i++) // 初始化第一行
dp[1][i]=jud(i);
for(int c=2; c<=n; c++) // 枚举 [2,n] 行
for(int i=0; i<=maxs; i++) // 第i行的状态
for(int si=0; si<=maxs; si++) //第i-1行的状态
if(ok(i,si))
dp[c][i]+=dp[c-1][si];
ans[n][m]=ans[m][n]=dp[n][maxs];
printf("%I64d\n",dp[n][maxs]);
} int main()
{
memset(ans,0,sizeof(ans));
while(cin>>n>>m&&(n||m))
{
if(ans[n][m]) //如果之前计算过,则直接给出结果
{
printf("%I64d\n",ans[n][m]);
continue;
}
if(n&1&&m&1) // 如果两边长都为奇数,则其面积也是奇数,无法放置
{
printf("0\n");
continue;
}
solve();
}
return 0;
}
//学习算法竞赛提升指南的写法
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll f[12][1 << 11];
int in_s[1 << 11];
int main() {
//freopen("in.txt", "r", stdin);
int n, m;
while (cin >> n >> m && n) {
for (int i = 0; i < 1 << m; ++i) {
bool cnt = 0, has_odd = 0;
for (int j = 0; j < m; ++j)
if (i >> j & 1) has_odd |= cnt, cnt = 0;
else cnt ^= 1;
in_s[i] = has_odd | cnt ? 0 : 1;
}
f[0][0] = 1;
for (int i = 1; i <= n; ++i)
for (int j = 0; j < 1 << m; ++j) {
f[i][j] = 0;
for (int k = 0; k < 1 << m; ++k)
if ((j & k) == 0 && in_s[j | k])
f[i][j] += f[i - 1][k];
}
cout << f[n][0] << endl;
}
}

在学习别人题解的时候发现一个DFS解决的,记录一下方法.

本质上和上面这个类似,但效率很高

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 11;
const int M = 10 + 20; int n, m, r;
ll dp[2][1 << N]; void dfs(int k, int u, int v) {
while (k < m && u & 1 << k) k++;
if (k >= m) {
dp[1 - r][v] += dp[r][u];
return;
}
dfs(k + 1, u, v | (1 << k));
if (k + 1 < m && !(u & (1 << k + 1))) {
dfs(k + 2, u, v);
}
} int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); while (cin >> n >> m && n && m) {
memset(dp, 0, sizeof(dp)); dp[1][0] = 1;
for (int i = 1; i <= n; i++) {
r = i & 1;
for (int s = 0; s < (1 << m); s++) {
if (dp[r][s]) dfs(0, s, 0);
}
memset(dp[r], 0, sizeof(dp[r]));
}
cout << dp[(n + 1) & 1][0] << endl;
} return 0;
}

炮兵阵地

思路:

待补

#include <iostream>
#include <cstring>
using namespace std;
#define MST(a, b) memset(a, b, sizeof(a));
#define CLR(a) MST(a, 0);
#define rep(x, y, z) for (int x = y; x < z; ++x)
const int INF = 0x3f3f3f3f;
int dp[101][77][77];
int sg[101];
int n, m, idx;
int s[77]; //合法摆放的集合
int cnt0[77]; //合法摆放方案的具体摆放个数, 即二进制下1的个数
int get_one(int x) {
int cnt = 0;
while (x) x &= (x - 1), ++cnt;
return cnt;
}
bool ok(int x) {
// 相邻两个P之间要有两个H
if (x & (x << 1)) return false;
if (x & (x << 2)) return false;
return true;
}
void init() {
idx = 0;
int end = 1 << m;
rep(i, 0, end) if (ok(i)) {
// s保存合法方案的集合
s[idx] = i;
// cnt0保存合法方案的摆放个数, 二进制位1的个数
cnt0[idx++] = get_one(i);
}
}
bool valid(int i, int x) {
if (sg[i] & x) return false;
return true;
}
int solve() {
int ans = 0;
MST(dp, -1);
dp[0][0][0] = 0;
rep(j, 0, idx) if (valid(1, s[j])) {
dp[1][j][0] = cnt0[j];
// 考虑n==1情况
ans = max(ans, dp[1][j][0]);
}
rep(i, 2, n + 1) {
// valid()函数判断, 第i行, 用方案s[j]是否合法
rep(j, 0, idx) if (valid(i, s[j])) {
// i行跟i-1行的方案, 满足, 互相炸不到对方
rep(k, 0, idx) if (valid(i - 1, s[k]) && (s[j] & s[k]) == 0) {
int last = 0;
// i-2行同上
rep(l, 0, idx) if (dp[i - 1][k][l] != -1 && (s[l] & s[j]) == 0 && valid(i - 2, s[l])) {
last = max(last, dp[i - 1][k][l]);
}
dp[i][j][k] = max(dp[i][j][k], last + cnt0[j]);
if (i == n) ans = max(ans, dp[i][j][k]);
}
}
}
return ans;
}
int main(int argc, char const* argv[]) {
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
rep(i, 1, n + 1) rep(j, 0, m) {
char tmp; cin >> tmp;
if (tmp == 'H') sg[i] |= (1 << (m - 1 - j));
}
init();
cout << solve() << endl;
return 0;
}

# 0x56 动态规划-状态压缩DP的更多相关文章

  1. [动态规划]状态压缩DP小结

     1.小技巧 枚举集合S的子集:for(int i = S; i > 0; i=(i-1)&S) 枚举包含S的集合:for(int i = S; i < (1<<n); ...

  2. 3.4 熟练掌握动态规划——状态压缩DP

    从旅行商问题说起—— 给定一个图,n个节点(n<=15),求从a节点出发,经历每个节点仅一次,最后回到a,需要的最短时间. 分析: 设定状态S代表当前已经走过的城市的集合,显然,S<=(1 ...

  3. [知识点]状态压缩DP

    // 此博文为迁移而来,写于2015年7月15日,不代表本人现在的观点与看法.原始地址:http://blog.sina.com.cn/s/blog_6022c4720102w6jf.html 1.前 ...

  4. Vijos 1002 过河 状态压缩DP

    描述 在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧.在桥上有一些石子,青蛙很讨厌踩在这些石子上.由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上 ...

  5. 状态压缩·一(状态压缩DP)

    描述 小Hi和小Ho在兑换到了喜欢的奖品之后,便继续起了他们的美国之行,思来想去,他们决定乘坐火车前往下一座城市——那座城市即将举行美食节! 但是不幸的是,小Hi和小Ho并没有能够买到很好的火车票—— ...

  6. [转]状态压缩dp(状压dp)

    状态压缩动态规划(简称状压dp)是另一类非常典型的动态规划,通常使用在NP问题的小规模求解中,虽然是指数级别的复杂度,但速度比搜索快,其思想非常值得借鉴. 为了更好的理解状压dp,首先介绍位运算相关的 ...

  7. 旅行商问题——状态压缩DP

    问题简介 有n个城市,每个城市间均有道路,一个推销员要从某个城市出发,到其余的n-1个城市一次且仅且一次,然后回到再回到出发点.问销售员应如何经过这些城市是他所走的路线最短? 用图论的语言描述就是:给 ...

  8. 状态压缩dp初学__$Corn Fields$

    明天计划上是要刷状压,但是作为现在还不会状压的\(ruoruo\)来说是一件非常苦逼的事情,所以提前学了一下状压\(dp\). 鸣谢\(hmq\ juju\)的友情帮助 状态压缩动态规划 本博文的大体 ...

  9. 浅谈状态压缩DP

    浅谈状态压缩DP 本篇随笔简单讲解一下信息学奥林匹克竞赛中的状态压缩动态规划相关知识点.在算法竞赛中,状压\(DP\)是非常常见的动规类型.不仅如此,不仅是状压\(DP\),状压还是很多其他题目的处理 ...

  10. 【算法】状态压缩DP

    状态压缩DP是什么? 答:利用位运算(位运算比加减乘除都快!)来记录状态,并实现动态规划. 适用于什么问题? 答:数据规模较小:不能使用简单的算法解决. 例题: 题目描述 糖果店的老板一共有M 种口味 ...

随机推荐

  1. Educational Codeforces Round 101 (Rated for Div. 2) E - A Bit Similar

    题目传送门 很巧妙的一道题.对于一个 \(n\)位的 \(01\)字符串,一共有 \(2^n\)种不同字符排列,对于任意一个固定排列,在 \(2^n\)种排列中只有一种排列与该固定排列处处不等,而题干 ...

  2. Gradio-Lite: 完全在浏览器里运行的无服务器 Gradio

    Gradio 是一个经常用于创建交互式机器学习应用的 Python 库.在以前按照传统方法,如果想对外分享 Gradio 应用,就需要依赖服务器设备和相关资源,而这对于自己部署的开发人员来说并不友好. ...

  3. FastJson、Jackson、Gson进行Java对象转换Json

    - Java对象转换Json的细节处理前言Java对象在转json的时候,如果对象里面有属性值为null的话,那么在json序列化的时候要不要序列出来呢?对比以下json转换方式一.fastJson1 ...

  4. k8s~envoy上添加wasm插件

    先查看这篇文章k8s~envoy的部署 当在Kubernetes中使用Envoy的WASM过滤器时,WASM过滤器会与Envoy一起部署在同一个Pod中,并与后端服务进行通信.以下是一个简单的关系图示 ...

  5. SpringBoot项目整合微信登录

    一.开通微信登录 去微信开发者平台 1.注册 2.邮箱激活 3.完善开发者资料 4.开发者资质认证 准备营业执照,1-2个工作日审批.300元 5.创建网站应用 6.提交审核,7个工作日审批 7.熟悉 ...

  6. Python+Selenium4自动化之JS属性

    应用场景 在自动化中, 能对JS代码进行增.删.改的话,可以帮助我们解决很多问题, 如:修改<a>标签的target属性,让它不打开新的窗口(_blank),从而不用频繁使用switch_ ...

  7. Python——第一章:语言介绍

    随着Python的语言在世界受欢迎程度持续高涨,如今也成功夺得了第一的宝座. 在计算机领域,没有谁(诺基亚.微软.苹果.亚马逊等等)能永远稳坐第一,随着时间更迭,就会推陈出新,一定会有更好的.更先进的 ...

  8. 使用Spring AI让你的Spring Boot应用快速拥有生成式AI能力

    之前分享了关于Spring新项目Spring AI的介绍视频.视频里演示了关于使用Spring AI将Open AI的能力整合到Spring应用中的操作,但有不少读者提到是否有博客形式的学习内容.所以 ...

  9. 聊一聊如何整合Microsoft.Extensions.DependencyInjection和Castle.Core(完结篇)

    前言 书接上回,上回我们了解了 castle 代理的一些缺点,本文将开始操作整合 Microsoft.Extension.Dependency和Castle,以让默认的容器可以支持拦截器 我们将以进阶 ...

  10. 【华为云技术分享】LwM2M协议的学习与分享

    [摘要] 本文主要对于LwM2M协议进行了简单的介绍,包括协议的体系架构以及特性.对象.资源.接口的定义等,希望对你有所帮助. 1协议简介 LwM2M(Lightweight Machine-To-M ...