解题报告
题目 :poj1185 poj2411
通过做poj1185和poj2411,初步学习了状态压缩Dp。重点还是如何设定状态和转移,只不过这个状态会用到维压缩的表示方法,这个做的原因就是这些问题的无后效性很难满足,因此我么你需要将这个问题中能够达到的状态都列举出来,其实回头想一想,01背包的解法就是我们对物品排列向体积状态的一个压缩。 状态压缩dp更加体现了记忆化搜索与dp的紧密联系。
Poj1182;
需要先处理对于一行来说,如果只有要求任意两个相邻差为2,我们搜索出方案数。(即一行内,满足任意两个相邻差为2的方案个数及方案是什么),然后我们定义状态f[i][j][k]表示第i行状态是j,第i-1行状态是k时前i行的最多能放的个数。转移方程
F[i][j][k] = max{f[i – 1][k][l] + one_num[j]} (one_num[j]表示在状态为j时,1的个数) 这是一个思维的转移,由于方案数很少,可以接受。
AC code
#include <cstdio>
#include <cstring>
#define MAXST 300
#define MAX(a, b) ((a > b) ? (a) : (b))
int esit[3000], state[MAXST], one_num[MAXST];
int f[2][MAXST][MAXST];
int able[110];
char str[110];
int n, m, stnum, SKS;
bool Can(int st, int i){
if(st & (1 << i)) return false;
if(i > 0 && (st & (1 << i - 1))) return false;
if(i > 1 && (st & (1 << i - 2))) return false;
if(i < m - 1 && (st & (1 << i + 1))) return false;
if(i < m - 2 && (st & (1 << i + 2))) return false;
return true;
}
int Get_one_num(int gt){
int ans = 0;
while(gt){
if(gt & 1) ans ++;
gt >>= 1;
}
return ans;
}
void BFS(){
int front;
front = stnum = 0;
memset(esit, 0, sizeof(esit));
state[stnum ++] = 0;
esit[0] = 1;
one_num[0] = 0;
while(front < stnum){
int i;
int st = state[front ++], gt;
for(i = 0; i < m; i ++){
if(Can(st, i)) gt = st ^ (1 << i);
else continue;
if(!esit[gt]){
esit[gt] = 1;
state[stnum] = gt;
one_num[stnum ++] = Get_one_num(gt);
}
}
}
}
int Dp(){
int i, j, k, l, max = 0;
if(n == 1){
for(j = 0; j < stnum; j ++)
if((state[j] & able[0])) continue;
else max = MAX(max, one_num[j]);
return max;
}
SKS = 0;
memset(f, 0, sizeof(f));
for(k = 0; k < stnum; k ++)
for(j = 0; j < stnum; j ++)
if((state[k] & able[0]) || (state[j] & able[1]) || (state[j] & state[k])) f[SKS][j][k] = 0;
else f[SKS][j][k] = one_num[j] + one_num[k];
for(i = 2; i < n; ++ i){
for(j = 0; j < stnum; ++ j)
for(k = 0; k < stnum; ++ k){
if((state[j] & able[i]) || (state[k] & able[i - 1]) || (state[j] & state[k])) continue;
for(l = 0; l < stnum; ++ l)
if((state[l] & able[i - 2]) || (state[l] & state[j]) || (state[l] & state[k])) continue;
else f[SKS ^ 1][j][k] = MAX(f[SKS ^ 1][j][k], f[SKS][k][l] + one_num[j]);
}
SKS ^= 1;
}
for(j = 0; j < stnum; j ++)
for(k = 0; k < stnum; k ++)
max = MAX(max, f[SKS][j][k]);
return max;
}
int main(){
int i, j, ans;
while(~scanf("%d %d", &n, &m)){
memset(able, 0, sizeof(able));
for(i = 0; i < n; ++ i){
scanf("%s", str);
for(j = m - 1; j >= 0; -- j){
if(str[j] == 'H')
able[i] |= 1 << (m - j - 1);
}
}
BFS();
ans = Dp();
printf("%d\n", ans);
}
return 0;
}
Poj2411;
这个题很明显要我们把两行连起来考虑,因为第i行竖着放的时候会影响到第一行,
状态转移 f[i][s1] = ∑f[i – 1][s2] (s1 与 s2 合起来后,必须s2饱和,s1可随意)
也就是说f[i][s1]表示的是当前i-1行全部覆盖,第i行状态为s1,第i-1行状态为s2时的方案数。横着放所覆盖两个格都为1,竖着放上面为零,下面为1.由于我们搜索时是在由s2推出s1,因此,i-1行是零的的位置表示没有被横着放覆盖住,需要我们在第i行用竖着放的方式去覆盖。因此我们需要规定:“竖着放上面为零,下面为1“,下面为1才会限制我们在第i行的操作。
AC code:
// 在枚举第i - 1行的状态时,我们已经确定第i行于第i - 1行对应的关系,如:第i-1行是1011011,
//那么这个意思是在3,6列(从左到右)第i和i-1行是竖着放的。那么我们只需要去搜索第i行1,2,4,5,7行水平放置或不放置的方法总数。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef __int64 I64;
I64 dp[20][2050];
int n, m;
void dfs(int i, int s, int p, I64 val){ //i:行号,s: 这一行的状态,p:列号, val:前i - 1行的方法总数
if(p == m){ dp[i][s] += val; return;}
dfs(i, s, p + 1, val); //第i行第p列不放置
if((p < m - 1) && !(s & (1 << p)) && !(s & (1 << (p + 1)))) dfs(i, s | (1 << p) | (1 << (p + 1)), p + 2, val);//第i行的p和p+1水平放置。
}
int main(){
int i;
while(~scanf("%d %d", &n, &m), n && m){
if((n & 1) && (m & 1)){printf("0\n"); continue;}
if(n < m) swap(n, m);
memset(dp, 0, sizeof(dp));
dfs(1, 0, 0, 1);
int t = 1 << m, j;
for(i = 2; i <= n; i ++)
for(j = 0; j < t; j ++) //枚举第i - 1行的状态
if(dp[i - 1][j]) dfs(i, (~j)&(t - 1), 0, dp[i - 1][j]); //第i - 1行在状态j下可行,那么去考虑第i行
printf("%I64d\n", dp[n][t - 1]);
}
return 0;
}
还要多做题啊,感觉理解的很不到位