状态压缩DP
动态规划的过程是随着“阶段”增长,在每个状态维度上不断扩展的。在任意时刻,已经求出的最优解的状态与尚未求出最优解的状态在各个维度的分界点组成了DP扩展的“轮廓”。
对于某些问题,我们需要在动态规划的“状态”记录一个集合,保存这个“轮廓”的详细信息,以便进行状态转移。若集合大小不超过N,集合中每个数都是小于K的自然数,则我们可以把集合看做一个N为K进制数,以一个[0, K^N - 1]之间的十进制整数的形式作为DP状态的一维。这种把集合转化为整数记录在DP状态中的一类算法,被称为状态压缩动态规划算法在求解最短Hamilton路径时,整个状态空间可以看作n维,每维代表一个节点,只有0(尚未访问)和1(已经访问)两个值。我们可以想象DP的“轮廓”以“访问过的节点数目”为阶段,从(0,0,...,0)扩展到(1,1,...,1)。为了记录当前状态在每个维度上的坐标是0还是1,我们使用一个N位二进制数,即[0, 2^n-1]之间的十进制整数存储节点的访问情况。另外,为了知道最后经过的节点是哪一个,我们把该节点编号作为附加信息,也保存在DP状态中。因此该状态压缩DP的状态数组就由大小分别为2^N和N的两个维度构成。 例题1: Mondriaan's Dream(poj2411)求把N*M(1 <= N,M <= 11)的棋盘分割成若干个1 * 2的长方形,有多少种方案。例如当N=2, M=3时,有3种方案。输入包含多个测试用例。每个测试用例由两个整数组成:大矩形的高度H和宽度W。输入由H=W=0终止。否则,1<h,w<11。
Sample Input
1 21 31 42 22 32 42 114 110 0
Sample Output
10123514451205
首先假设我们要对棋盘进行切割,有横切和纵切这两种思路,按照DP的思想,为了方便,将每一行划分为阶段,由上一行对下一行进行扩展,
这样我们想对于这一行的切割,有一次切出1*2的长方形和先切出1*1两种方式例如:1 #include2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #define ll long long10 using namespace std;11 int n, m;12 ll f[12][1 << 11];13 bool vis[1 << 11]; 14 int main() {15 while(cin >> n >> m && n) {16 memset(vis, 0, sizeof(vis));17 memset(f, 0, sizeof(f));18 for(int i = 0; i < 1 << m; ++i) {19 bool flag1 = 0, flag2 = 0;20 for(int j = 0; j < m; ++j) {21 if(i >> j & 1) flag1 |= flag2, flag2 = 0;22 else flag2 ^= 1;//只要0不是连续的,就激活flag1(flag1 = 1),则此时无论后面flag2为多少,最终都不满足 23 }24 vis[i] = flag1 | flag2 ? 0 : 1;//flag1 | flag2 == 1,取0,否则取1 25 }26 f[0][0] = 1;27 for(int i = 1; i <= n; ++i) {28 for(int j = 0; j < 1 << m; ++j) {29 f[i][j] = 0;30 for(int k = 0; k < 1 << m; ++k) 31 if((j & k) == 0 && vis[j | k]) 32 f[i][j] += f[i - 1][k];33 }34 }35 cout << f[n][0] << '\n';//最后分割完的大矩形,一定不存在空缺,其M位二进制数表示一定为0 36 }37 return 0;38 }
还是很难想的啊=-=
例题2:炮兵阵地
题目描述
司令部的将军们打算在N*M的网格地图上部署他们的炮兵部队。一个N*M的地图由N行M列组成,地图的每一格可能是山地(用“H” 表示),也可能是
平原(用“P”表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其 它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。 现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部 队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。输入输出格式输入格式:第一行包含两个由空格分割开的正整数,分别表示N和M;接下来的N行,每一行含有连续的M个字符(‘P’或者‘H’),中间没有空格。按顺序表示地图中每一行的数据。N≤100;M≤10。输出格式:仅一行,包含一个整数K,表示最多能摆放的炮兵部队的数量
题意:在矩形中放下尽量多的十字,并且每个十字的中心不能被别的十字覆盖
将行作为阶段每个位置能否防止炮兵和他上面两行的对应位置相关,所以向第i行扩展时,需要知道i-1行和i-2行的状态。我们把每一行的情况用一个[0, 2^M-1]的M位二进制数表示,对于第i个二进制数,第p位为1,表示第i行的第p列放了炮兵,第p位为0,表示第i行第p列没 放炮兵(被覆盖或是有山)我们先预处理出集合S,存储“相邻两个1的距离不小于3”的所有M位二进制数,表示每一行的炮兵距离不小于3 同样为了进行转移:我们还需要知道每一行有多少个1 设count(x)表示M位二进制数x中1的数量 设vaild(i, x)表示M位二进制数x属于集合S,并且x中的每个1对应在地图第i行的位置是平原。设f[i, j, k]表示第i行压缩后的状态为j,第i-1行压缩后状态为k时,前i行最多能摆放多少炮兵f[i, j, k] = max{f[i - 1, k, l] + count(j)}(vaild(i, j) && vaild(i - 1, k) && j &k = 0)初值:f[0, 0, 0] = 0,其余均为负无穷目标:max(f[n, j, k])(j|k == 0 && vaild(N,j),vail(N-1,k))最初存图时,我们把平原存为0,山丘存为1
然后在判断某个情况是否符合是,我们将二进制数与原来的a[i]相&,最后若得到的结果不是0,就说明在这个是山的位置出现了炮兵阵地,不合法,直接 continue;判断每个状态有没有两个炮兵的距离在两格之内如果把当前状态的二进制数位左移一位,将这个结果与原状态做一次&运算,结果若不是0,就说明在两格之内的距离内,被放置了炮兵,就是非法状态, 同理我们在左移两格相&进行判断1 #include2 using namespace std; 3 int n, m, ans = 0xcfcfcfcf; 4 int a[1 << 10], sum[1 << 10]; 5 int f[4][1 << 10][1 << 10]; 6 char ch; 7 8 inline int read() { 9 int x = 0, y = 1;10 char ch = getchar();11 while(!isdigit(ch)) {12 if(ch == '-') y = -1;13 ch = getchar();14 }15 while(isdigit(ch)) {16 x = (x << 1) + (x << 3) + ch - '0';17 ch = getchar();18 }19 return x * y;20 }21 22 int main() {23 memset(f, 0xcfcfcf, sizeof(f));24 n = read(), m = read();25 for(int i = 1; i <= n; ++i) 26 for(int j = 1; j <= m; ++j) {27 cin >> ch;28 a[i] <<= 1;29 a[i] += (ch == 'H') ? 1 : 0;30 //a[i] <<= 1;//把这个放在这个位置就会wa=-= 31 }32 for(int i = 0; i < 1 << m; ++i) {33 int sum1 = 0;34 for(int j = 0; j < m; ++j)35 if(i >> j & 1) sum1++;36 sum[i] = sum1;37 }38 f[0][0][0] = 0;39 for(int i = 1; i <= n; ++i) {40 for(int j = 0; j < 1 << m; ++j) { 41 if(j & (j << 1) || j & (j << 2) || j & a[i]) 42 continue;43 for(int k = 0; k < 1 << m; ++k) {44 if(j & k || k & a[i - 1] || k & (k << 1) || k & (k << 2))45 continue;46 for(int l = 0; l < 1 << m; ++l) {47 if(j & l || k & l || l & a[i - 2] || l & (l << 1) || l & (l << 2))48 continue;49 f[i%3][j][k] = max(f[i%3][j][k], f[(i - 1)%3][k][l] + sum[j]);50 }51 }52 }53 }54 for(int i = 0; i < 1 << m; ++i)55 for(int j = 0; j < 1 << m; ++j)56 ans = max(ans, f[n%3][i][j]);57 cout << ans << '\n';58 return 0;59 }
但是炮兵阵地这道题,我的写法并不优美,跑起来非常的慢=-=建议不要看