状态压缩DP
概念
1. 什么是状态压缩
状态压缩顾名思义便是把一大堆状态压缩成二进制数,节省了空间
2. 如何状态压缩
用
例题
1. 最短Hamilton路径
题意:求一笔画一张无权图的长度的最小值
思路:通过枚举从i经过所有点到j的情况长度,求出最小值
闫氏dp分析法:
其中
是二进制的状态, 是目前到了的点
状态转移方程:
表示除去 后的所有路径 —— 1 << j用二进制表示就是
就是从i到k的最短长度加上k到j的路程
码来!
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 20, M = 1 << N; // state需要的空间+1
int f[M][N], g[N][N], n; // 邻接矩阵存储图
int main()
{
scanf("%d", &n);
for(int i = 0; i < n; i++)
for (int j = 0; j < n; j ++ )
scanf("%d", &g[i][j]);
memset(f, 0x3f, sizeof f); // 普通的初始化
f[1][0] = 0; // 对于起点路程是0
for(int i = 0; i < 1 << n; i++) // 枚举所有状态
for (int j = 0; j < n; j ++ ) // 枚举所有当前点
if(i >> j & 1) // 判断j是否走过
for(int k = 0; k < 0; k++) // 枚举所有要经过的点
if(i >> k & 1) // 判断k是否走过
f[i][j] = min(f[i][j], f[i - (1 << j)][k] + g[k][j]); // 状态转移方程
printf("%d", f[(1 << n) - 1][n - 1]);
return 0;
}
2. 蒙德里安的梦想
题意:求用1*2的长方形去填一个棋盘的方案数
思路:一列一列枚举,看每一列中有多少个合法的方案
闫氏dp分析法:
状态转移方程:
即枚举上一列的状态(k)所有的可能情况
注意:需要判断情况的合法性,如图,当从第列伸到第 列的方块是躺着的,从第 列伸到第 列的方块也是躺着的时,就会重叠,也就是 时才是合法的 两数进行&运算时,只有每一位相同结果才是0
表示 到 的状态和 到 的状态总共有多少个1, 则用于判断i-1这一行是否合法
暴力代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 12, M = 1 << N;
long long f[N][M];
bool st[M];
int n, m;
int main()
{
while(cin >> n >> m, n || m)
{
for (int i = 0; i < 1 << n; i ++ )
{
int cnt = 0; // cnt统计连续0的个数,如果有奇数个0就是不合法情况
st[i] = true; // st[]相当于记录每一列的cnt
for (int j = 0; j < n; j ++ )
{
if(i >> j & 1) // 如果i的第j位是1,也就是第j行有伸出来的方格
{
if(cnt % 2 == 1)
{
st[i] = false; // 当0的个数是奇数时就记录下来这种情况不合法,也可以写为cnt & 1
break;
}
cnt = 0; // 判断过一段了就可以从0开始重新统计
}
else cnt ++; // 因为i的第j位是0所以cnt++
}
if(cnt % 2 == 1) st[i] = false; // 在末尾还要进行一次判断,避免错过最后一段连续的0
}
memset(f, 0, sizeof f); // 将所有状态初始化为0
f[0][0] = 1; // 回顾集合定义,由于第-1列不存在所以自然而然地只有一种方案 —— 全立着
for(int i = 1; i <= m; i++) // 枚举列
{
for(int j = 0; j < 1 << n; j ++) // 枚举i-1到i的状态
{
for (int k = 0; k < 1 << n; k ++ ) // 枚举i-2到i-1的状态
{
if((j & k) == 0 && st[j | k]) // 如果是合法的
f[i][j] += f[i-1][k]; // 状态转移
}
}
}
cout << f[m][0] << endl;
// 回顾集合定义:
// f[m][0]代表m-1已经确定,从m-1伸向m的状态是00000,也就是没有伸向m的方块,棋盘只有0~m-1列
}
return 0;
}
进行一个小优化:预处理每一次的合法状态,最后直接用就好了
码来!
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 12, M = 1 << N;
long long f[N][M];
vector<int> state[M]; // 这个数组预处理存储合法状态
bool st[M];
int n, m;
int main()
{
while(cin >> n >> m, n || m)
{
for (int i = 0; i < 1 << n; i ++ )
{
int cnt = 0;
bool &isValid = st[i]; // 引用
isValid = true;
for (int j = 0; j < n; j ++ )
{
if(i >> j & 1)
{
if(cnt % 2 == 1)
{
isValid = false;
break;
}
cnt = 0;
}
else cnt ++;
}
if(cnt % 2 == 1) isValid = false;
}
// ========这就是优化的东西=========
for(int i = 0; i < 1 << n; i++) // 预处理合法状态
{
state[i].clear(); // 每组数据的state是独立,所以每次都要清除一遍
for(int j = 0; j < 1 << n; j++)
{
if((i & j) == 0 && st[i | j]) // 此处只是把原来的j和k改成了此处的i和j
state[i].push_back(j); // 把合法的j推进去
}
}
// ========这就是优化的东西=========
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;
}
题图也是Hamilton,谐音梗((((别打我
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!