一、AcWing 291. 蒙德里安的梦想
求把 N×M 的棋盘分割成若干个 1×2的长方形,有多少种方案。
例如当 N=2,M=4时,共有 5 种方案。当 N=2,M=3 时,共有 3种方案。
如下图所示:
1.1问题分析
我们把这个切割问题转化成为摆长方形的问题,1x2的长方形,既可以竖着摆,也可以横着摆。我们只用分析出哪些位置横着摆,如果剩下的位置就只能竖着摆,则该答案为可行解。
- 首先,如何确定位置是否已经被摆放?引入01变量的就行了,学过数学建模的同学都知道吧。
状压dp的建模方式往往是引入0/1变量
- 如何枚举每种情况?既然都是01整数规划了,肯定用位运算啊(),即1<<n
咳咳,回到本题,首先我们要确保每个横向摆放的矩形之间的纵向举例为偶数,此处是纵向枚举。
横向枚举时,保证原本横着摆的矩形不重叠。
嗯?上述操作中前者可以数据预处理吧,那就先预处理吧,多点常数,时间复杂度确降低了。
1.2模型建立
这题用DP就行了,
f
[
i
]
[
j
]
f[i][j]
f[i][j]为表示从i-1列伸到i列,且所有行状态为j的状态,
集合划分的依据是所有i-2列伸到i-1列,且所有行状态为k的状态。
对于k行而言,如果合法,则可以累加,合法条件上面已经提到了。
即:
f
[
i
]
[
j
]
+
=
f
[
i
−
1
]
[
k
]
;
f[i][j]+=f[i-1][k];
f[i][j]+=f[i−1][k];
此处第二维表示的是状态,故叫作状压DP。
1.3模型求解
DP问题初始化一直是个难点,上述状态转移方程可能会使
i
=
0
i=0
i=0(越界状态),我们不妨初始化
i
=
0
i=0
i=0时f的值。这里
f
[
0
]
[
0
]
=
1
f[0][0]=1
f[0][0]=1,表示只能横着摆,这样其他所有状态都可以递推出来了~
预处理的过程是一个模拟的过程,所以要考虑周全,特别是边界问题。
#include <bits/stdc++.h>
using namespace std;
const int maxn=12,maxm=1<<12;
typedef long long LL;
int n,m;
LL f[maxn][maxm]; //f[i][j]表示从i-1列伸到i列,且所有行状态为j的状态
int mark[maxm];
inline bool is_odd(int num)
{
return num%2==1;
}
/*
集合的划分依据就是所有i-2列伸到i-1列,且所有行状态为k的状态
*/
int main()
{
while(cin>>n>>m &&((n!=0)&&(m!=0)))
{
memset(mark,0,sizeof(mark));
for(int i=0;i<1 << n;i++) //枚举每种状态,判断是否会发生冲突(模拟)
{
int cnt=0;
for(int k=0;k<n;k++)
{
if(i>>k & 1 ==1)
{
if(is_odd(cnt))
{
mark[i]=true; //非法
break;
}
}
else
cnt++;
}
if(is_odd(cnt)) mark[i]=true;
}
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(!(k&j) && !(mark[k|j])) //在合法的条件下进行状态转移
f[i][j]+=f[i-1][k];
}
}
}
cout<<f[m][0]<<endl;
}
return 0;
}
二、AcWing 91. 最短Hamilton路径
给定一张 n个点的带权无向图,点从 0∼n−1 标号,求起点 0 到终点 n−1的最短 Hamilton 路径。
Hamilton 路径的定义是从 0到 n−1 不重不漏地经过每个点恰好一次。
本题特性:
每个点是否经过就是一个01变量,即n个点的01变量的组合为i,
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示经过j个点时已然经过的路线状态为i。
2.1模型建立
然后就是一个集合的状压dp,当然,本质上还是一个01规划问题。
f
[
i
]
[
j
]
=
m
i
n
{
f
[
i
−
{
k
}
]
[
k
]
+
w
[
k
]
[
j
]
,
f
[
i
]
[
j
]
}
f[i][j]=min\{f[i-\{k\}][k]+w[k][j],f[i][j]\}
f[i][j]=min{f[i−{k}][k]+w[k][j],f[i][j]}
2.2模型求解
1、枚举的顺序别搞错了,先枚举第一维,再枚举第二维。根据本题的特性,第一维是枚举状态。
2、位运算枚举搜索顺序
#include <bits/stdc++.h>
using namespace std;
const int maxn=21;
const int maxm=1<<21;
int f[maxm][maxn];
int g[maxn][maxn];
int main()
{
int n;
cin>>n;
memset(f,0x3f,sizeof(f));
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
cin>>g[i][j];
}
f[1][0]=0;
for(int i=0;i< 1<<n;i++)
{
for(int j=0;j<n;j++)
{
if(i>>j&1)
{
for(int k=0;k<n;k++)
{
if(i>>k&1)
{
f[i][j]=min(f[i][j],f[i-(1<<j)][k]+g[k][j]);
}
}
}
}
}
cout<<f[(1<<n)-1][n-1];
return 0;
}