状态压缩dp整理
创始人
2024-02-10 10:08:46

目录

  • 蒙德里安的梦想
    • 详细解释
    • Code
  • 最短Hamilton路径
    • 详细解释
    • Code


蒙德里安的梦想

求把 N×MN×MN×M 的棋盘分割成若干个 1×21×21×2 的长方形,有多少种方案。

例如当 N=2,M=4N=2,M=4N=2,M=4 时,共有 555 种方案。当 N=2,M=3N=2,M=3N=2,M=3 时,共有 333 种方案。

如下图所示:

在这里插入图片描述

输入格式
输入包含多组测试用例。

每组测试用例占一行,包含两个整数 N和MN 和 MN和M。

当输入用例 N=0,M=0N=0,M=0N=0,M=0 时,表示输入终止,且该用例无需处理。

输出格式
每个测试用例输出一个结果,每个结果占一行。

数据范围

1≤N,M≤111≤N,M≤111≤N,M≤11

输入样例:

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

输出样例:

1
0
1
2
3
5
144
51205

详细解释

这题最最核心的一点是要想到:横着摆放方块的方案数 = 总摆放方块方案数

由于1×21×21×2的方块可以横着或是竖着放,观察图给样例,如果把所有能横着放的方块都放入后,竖着放的方块位置也就固定了,只管往里塞,于是结论就是要找总方案数只需要找到可以横着摆放方块的方案数。

状态表示

f[i][j]:前i - 1列的摆放状态已处理好的前提下,在i - 1列横放的小方块伸到i列的格子状态为j的方案数。
其中j由于是一个状态表示,所以要把它想成一个二进制数,它的范围是由nnn(一列有nnn行)决定的,即为了表示nnn个格子需要有 2n2^n2n 个状态,即j∈(0,1<


格子的摆放引起状态

这个状态定义有点长,什么意思呢?以下图为例:
表示当i=2(i从0开始)i=2(i从0开始)i=2(i从0开始),j=(11001)2j = (11001)_2j=(11001)2​时的状态,jjj中为111的地方表示该列的这个位置被占了

在这里插入图片描述

其中尤其要注意的是:第iii列的jjj这个状态本质上是由于第i−1i-1i−1列状态造成影响的,也就是说,jjj中为1的地方在i−1i-1i−1列的对应位置上是横放了方块之后捅到第 iii 列去的

以此类推,如果定义i−1i-1i−1列上的状态是kkk,那么这个kkk是经过i−2i-2i−2列的方块放置影响到的,就像下图:

在这里插入图片描述

那么绿色的方块是在i−2i-2i−2列放置导致的,此时的 k=(00100)2k=(00100)_2k=(00100)2​,注意≠11101≠11101​=11101。


状态计算

由于动态规划是按列数从小到大(0到m−10到m-10到m−1)挨着枚举状态,因此第 iii 列的方案数f[i][j],由前一列的方案数f[i - 1][k]转移而来,即f[i][j] = Σ(f[i - 1][k])


判断合法状态

很明显不是所有格子摆放状态都是合法的。

  1. 每一列的状态中连续空着的格子数不能为奇数,因为空格是给竖着放的方块留着的,方块尺寸1×21×21×2,当要竖着放时遇到奇数空格注定是要么放不满要么放不下。用一个st[]数组存,如果状态 jjj 连续空着的格子数为偶数st[j] = true。而状态转移时是要判断方块从i−2i - 2i−2列合在第i−1i - 1i−1列的方块共有多少个,可以“或”运算,即判断st[j | k]
  2. i - 2列伸到i - 1列的小方格 和i - 1列放置的小方格不能重复,啥意思呢?看下图 请添加图片描述
    可以看到,此时蓝色的方块和红色的方块 “撞” 在了一起,显然非法的,必须得让jjj和kkk中的“1”岔开,这里判断用到一个小技巧,j & k == 0则意味着相同位置不会同时出现1。

最终,整块拼图的方案数存在了f[m][0],0表示(00...0)2(00...0)_2(00...0)2​,它意味着mmm列不放小方格,前m−1m-1m−1列已经完全摆放好并且不伸出来的状态。那么意味着拼图完整。

请添加图片描述


Code

#include 
#include 
using namespace std;
typedef long long ll;const int N = 12, M = 1 << N;       //N表示列数、M表示状态的范围(0b00...0 ~ 0b10...0)ll f[N][M];        //f[i][j]:第i列的状态为j时的方案数
bool st[M];          //st[j]映射状态j是否满足“没有奇数个连续的空格”这个条件
int n, m;int main(){while(cin >> n >> m, n || m){//第一步预处理,装填st数组for(int i = 0;i < 1 << n;i ++){     //枚举每个状态int cnt = 0;bool valid = true;for(int j = 0;j < n;j ++){if((i >> j) & 1){if(cnt & 1){     //如果有奇数个连续的空格valid = false;break;}cnt = 0;}else    cnt ++;}if(cnt & 1)     valid = false;      //统计最后一堆连续空格(如果有)st[i] = valid;}memset(f, 0, sizeof f);f[0][0] = 1;		//因为没有-1列,第0列至少有一种摆放方案for(int i = 1;i <= m;i ++)         //枚举每一列for(int j = 0;j < 1 << n;j ++)          //枚举j,把j视作从i - 1列捅到i列的格子状态for(int k = 0;k < 1 << n;k ++)      //枚举k,把k视作从i - 2列捅到i - 1列的格子状态if((j & k) == 0 && st[j | k])f[i][j] += f[i - 1][k];cout << f[m][0] << endl;            }return 0;
}

最短Hamilton路径

给定一张 nnn 个点的带权无向图,点从 0∼n−10∼n−10∼n−1 标号,求起点 000 到终点 n−1n−1n−1 的最短 Hamilton 路径。

Hamilton 路径的定义是从 000 到 n−1n−1n−1 不重不漏地经过每个点恰好一次。

输入格式
第一行输入整数 nnn。

接下来 nnn 行每行 nnn 个整数,其中第 iii 行第 jjj 个整数表示点 iii 到 jjj 的距离(记为 a[i,j]a[i,j]a[i,j])。

对于任意的 x,y,zx,y,zx,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x]并且a[x,y]+a[y,z]≥a[x,z]a[x,x]=0,a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]≥a[x,z]a[x,x]=0,a[x,y]=a[y,x]并且a[x,y]+a[y,z]≥a[x,z]。

输出格式
输出一个整数,表示最短 Hamilton 路径的长度。

数据范围
1≤n≤201≤n≤201≤n≤20
0≤a[i,j]≤1070≤a[i,j]≤10^70≤a[i,j]≤107

输入样例:

5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0

输出样例:

18

详细解释

这里有一篇写得很棒的解释👉 最短Hamilton路径(超详解)。
偷个懒吧hh。

状态表示

f[i][j]: 所有从0走到j,经过的路径是i(二进制数)的最短距离。
举个例子:i = 11001j = 4时,1表示走到,0表示没走到,那么状态i代表的路径就是0 —> 3 —> 4,那么f[0b11001][4]就代表从0走到4,路径是0 —> 3 —> 4的最短距离。
因此,答案就存在f[0b111...1][n - 1] = f[(1 << n) - 1][]n - 1里面。

状态计算

状态计算用到的算法就很类似于floydfloydfloyd算法,即

  1. 暴力地枚举每种可能的走法i
  2. i的走法下,暴力地枚举每一个点j作为终点的情况
  3. j作为终点的基础上,然后再暴力地枚举每一个点k(这个点也可以想象成floydfloydfloyd里的那个k),目的是把k视作一个中间点(从位置上准确地讲,k0走到j之前到达的最后一个点)
  4. 然后经过各种0 —> (一系列不包含终点 j 的点) —> k —> j路径来对f[i][j]进行松弛操作,也就是f[i][j] = min(f[i][j], f[i - (1 << j)][k] + w[k][j])其中i - (1 << j)对应的就是 “一系列不包含终点 jjj 的点”

Code

#include 
#include 
using namespace std;const int N = 20, M = 1 << N;int w[N][N];        //距离矩阵
int f[M][N];        //f[i][j]:所有从0走到j,经过的路径是i(二进制数)的最短路径
int n;int main(){cin >> n;for(int i = 0;i < n;i ++)for(int j = 0;j < n;j ++)cin >> w[i][j];memset(f, 0x3f, sizeof f);f[1][0] = 0;for(int i = 0;i < 1 << n;i ++)      //枚举所有的走法for(int j = 0;j < n;j ++)       //枚举j,将j认为是终点,当然了,是为了能将状态转移到n - 1这个点if(i >> j & 1)      //点在路径上的状态是1才说明“走到了”并进行处理for(int k = 0;k < n;k ++)      //枚举k,将k认为是走到j之前的最后一个点if(i >> k & 1)		//点在路径上的状态是1才说明“走到了”并进行处理f[i][j] = min(f[i][j], f[i - (1 << j)][k] + w[k][j]);      //松弛操作cout << f[(1 << n) - 1][n - 1] << endl;return 0;
}

相关内容

热门资讯

猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...