#include <stdio.h>
long combineNum[12][2049];
long long combineNumLastRow[12][2049];
// void getCombineNum() {
// for (int i = 1; i <= 11; i++) {
// for (int j = 0; j <= 2047; j++) {
// if (j > pow(2, i) - 1) {
// combineNum[i][j] = 0;
// } else if (i == 1) {
// combineNum[i][j] = 1;
// } else if (i == 2) {
// if (j == 0) {
// combineNum[i][j] = 2; // 1. -- 2. ||
// } else if (j == 1) {
// combineNum[i][j] = 1;
// } else if (j == 2) {
// combineNum[i][j] = 1;
// } else if (j == 3) {
// combineNum[i][j] = 1;
// }
// } else {
// int first2Bit = j & 0x3;
// switch (first2Bit) {
// case 0:
// combineNum[i][j] = combineNum[i-1][j>>1] + combineNum[i-2][j>>2];
// break;
// case 1:
// combineNum[i][j] = combineNum[i-1][j>>1];
// break;
// case 2:
// combineNum[i][j] = combineNum[i-2][j>>2];
// break;
// case 3:
// combineNum[i][j] = combineNum[i-2][j>>2];
// break;
// }
// }
// }
// }
// }
void getCombineNumLastRow() {
for (int i = 1; i <= 11; i++) {
for (int j = 0; j <= 2047; j++) {
if (j > (2<<(i-1)) - 1) {
combineNumLastRow[i][j] = 0;
} else if (i == 1) {
if (j == 0) {
combineNumLastRow[i][j] = 0;
} else if (j == 1) {
combineNumLastRow[i][j] = 1;
}
} else if (i == 2) {
if (j == 0) {
combineNumLastRow[i][j] = 1; // 1. --
} else if (j == 1) {
combineNumLastRow[i][j] = 0;
} else if (j == 2) {
combineNumLastRow[i][j] = 0;
} else if (j == 3) {
combineNumLastRow[i][j] = 1;
}
} else {
int first2Bit = j & 0x3;
switch (first2Bit) {
case 0:
combineNumLastRow[i][j] = combineNumLastRow[i-2][j>>2];
break;
case 1:
combineNumLastRow[i][j] = combineNumLastRow[i-1][j>>1];
break;
case 2:
combineNumLastRow[i][j] = 0;
break;
case 3:
combineNumLastRow[i][j] = combineNumLastRow[i-2][j>>2];
break;
}
}
}
}
}
bool checkIfMatch(int upRowStatus, int currentRowStatus, int width, int rowMax) {
if ((upRowStatus¤tRowStatus) != 0) {
return false;
}
int tmp = currentRowStatus;
int tmp1 = upRowStatus;
int zeroNum = 0;
int i;
for (i = 0; i < width; i++) {
char lowBit = 0x00000001 & currentRowStatus;
char upperLowBit = 0x00000001 & upRowStatus;
if (upperLowBit == 1) {
if (lowBit == 1) {
return false;
} else {
if (zeroNum%2 == 0) {
zeroNum = 0;
} else {
return false;
}
}
} else {
if (lowBit == 1) {
if (zeroNum%2 == 0) {
zeroNum = 0;
} else {
return false;
}
} else if (lowBit == 0) {
zeroNum++;
}
}
currentRowStatus = currentRowStatus>>1;
upRowStatus = upRowStatus>>1;
}
if (i == width) {
if (zeroNum%2 != 0) {
return false;
}
}
return true;
}
bool checkIfValidFirstRow(int rowStatus, int width, int rowMax) {
int tmp = rowStatus;
int zeroNum = 0;
int i;
for (i = 0; i < width; i++) {
char lowBit = 0x00000001 & rowStatus;
if (lowBit == 1) {
if (zeroNum%2 == 0) {
zeroNum = 0;
} else {
return false;
}
} else if (lowBit == 0) {
zeroNum++;
}
rowStatus = rowStatus>>1;
}
if (i == width) {
if (zeroNum%2 != 0) {
return false;
}
}
return true;
}
void getCount(int height, int width) {
int rowMax = (2<<(width-1)) - 1;
long long DP[12][2049] = {0};
if (height == 1) {
printf("%lld\n", combineNumLastRow[width][0]);
return;
}
for (int i = height - 1; i >= 1; i--) {
for (int j = 0; j <= rowMax; j++) {
if (i == height - 1) {
DP[i][j] = combineNumLastRow[width][j];
} else {
long long sum = 0;
int addtime = 0;
for (int k = 0; k <= rowMax; k++) {
if (checkIfMatch(j, k, width, rowMax)){
// if (j == 1 && i == 1) {
// printf("check %d %lld\n", k, DP[i+1][k]);
// }
sum += DP[i+1][k];
addtime++;
}
}
// if (i == 1) {
// printf("j %d addtime %d\n", j, addtime);
// }
DP[i][j] = sum;
}
}
}
long long sum = 0;
for (int i = 0; i <= rowMax; i++) {
if (checkIfValidFirstRow(i, width, rowMax)) {
sum += DP[1][i];
// printf("%lld i %d\n", DP[1][i], i);
}
}
printf("%lld\n", sum);
}
int main() {
getCombineNumLastRow();
while(1) {
int height, width;
scanf("%d %d", &height, &width);
if (height == 0 && width ==0) {
return 0;
}
getCount(height, width);
}
}
2097ms
憋了几天, 正好工作又忙, 这道题彻底认识到了自己在思维全面性的不足。
用的是常规做法 压缩状态的DP, 压缩状态其实到还不是什么高阶技能, 之前做水题涉及到存储一个小矩形的时候已经用上了,说白了就是编码的本质,
二进制罢了.
关键我最开始根本没有想到以行为单位进行DP, 老想着以一格为单位进行分析, 这样的话, 就算压缩状态也救不了我, 11*11个位, 用128位变量?, 绝对不是这道题的做法。
后来忍不住稍微搜了下, 才发现可以以行为单位分析(唉, 组合数学要补呀, 当时咋就没想到呢, 当然, 用列也可以, 但是本质上是一样的)
某一行的组合状态是一个特定状态, 那么整个长方形的组合状态就是一个特定状态, 不管其他行怎么的排法, 其实相同了很简单, 组合的本质嘛, 某一个特定位置的特定值就可以表示一个整的特定状态(当然, 是从数量上讲).
然后就是该怎么表示了, 很自然的想到, 横排的格子全部为0, 竖排的全部为1, 咋一看很合理, 但是后来发现, 这样的话,根本体现不出来上一行对下一行的影响了。
比如
1100
这一行, 他既可以是 两个向上竖排加上一个横排, 也可以是两个向下竖排将上一个横排, 下一行的状态不能被上一行正确的影响了。后来钻了牛角尖, 甚至想到统计当前行的某一列累计1和0的奇偶性来表示向上和向下横排, 后来也作罢。
后来才想到, 从对下一排的排列有无直接影响作为区分, 如果是横排说这上竖排,那么显然对下一行的对应列没有影响, 那么就可以用0表示, 而下竖排显然影响了下一行对应列(下行对应列不能再放了), 那么就是1.
得到了表示方式, 下一步就是求上一行是这种状态, 下一行能有多少个可能的状态了, 这一步思想也简单,只要这一行为1的列(下竖排)和上一行是1的列(上竖排)之间的空隙都是偶数个0(不然如果是奇数个零,那么必有一个空格没法用长度为2的横排填)就是满足需求的组合, 特殊的是最后一行, 最后一行不能再有下竖排, 那么就必须是上一行上竖排的对应列之间的0数为偶数. 最后一行的每种状态组合数记下来,combineNumLastRow。
这样利用DP
第i行(第一行要特殊处理, 不能有上竖排)某个状态j, D[i][j] 就有这样的规律:
如果只有一行(可以认为上一行对齐没有任何影响, 0这个状态), 那么直接用前面求出最后一行为0的可能组合数就可以了.
如果i是倒数第二行, 那么D[i][j] = combineNumLastRow[width][j], 及最后一行在长度为width的情况下, 上一行为j的情况下的组合数。
对于其他的行i, 这时候, 就体现出空间换时间了, 既可以把此状态对应的下一行可能的状态保存(花费空间), 直接遍历, 也可以直接遍历k: 0 ~ (2<<width-1)-1,
逐个排查(花费时间)是否能适应当前行的状态, 如果能, 那么就加上此下一行状态D[i+1][k]的组合数。
在最后求总的组合数, 也不能简单的把D[1][k](k: 0 ~ (2<<width-1)-1 ) 相加, 要注意,第一行和中间行也是不一样的, 第一行是不能有上竖排的, 因此, 在累加的时候也要做一次筛选(其实这一步应该是可以省去的, 应该有更好的办法,在DP的时候就跳过去)。
最后终于以2097mx AC,和期望的差不多, 必定是耗时的。
看disscuss有神人用了25行搞定, 一定要研究研究。
这道题, 如果位运算用的熟练的话,是用不了那么多代码的. 看样子还只有刷题才能提高呀, 工作这几年,基本是遇不到这些需求的,更多的是一种实现的规划和组织. 不过也没准是屠龙之技?算了,
好歹找工作有用.