1. 题目来源
2. 题目说明
3. 题目解析
方法一:递推+状压dp+巧妙解法
就为啥我还能把样例 1 的图示看成 3 列呢,结果就把题意理解错了…纠结了好久…真吐了。
主要思想为递推、状压 dp
,下面简单理下思路:
- 状态定义:
dp[i][bits]
已经填色到第i
行,i
行填色方案为bits
的方案数。在此要注意bits
为 6 位二进制,通过简单位运算即可将其切割为三个[0,4)
,即用 01 位,23 位, 45 位表示三个格子的 3 种颜色情况,并将数字 3 废弃,利用 0,1,2 表示这三种颜色 - 首先对第一行进行初始化,由于是 6 位二进制,那么就会产生 64 种可能填色的情况,遍历这 64 种情况即可。判断时有两个条件需要满足:3 不在颜色种类内,并且相邻颜色不能相等。满足这两个条件,则将第一行
bits
填色方案结果初始化为 1 - 然后递推计算剩下的所有行即可,当前行也会产生 64 种情况,遍历计算。在此需要注意,若当前行的某种填色方案已经合法了,就需要判断是否与上一行的填色方案产生了冲突,即判断同列值是否相等
- 并且值得注意的是,若前一行的填色方案不冲突但是填色方案为 0 时,就直接
continue
,寻找下一个填色方案。为什么呢?在此有两种情况需要考虑- 该方案不合法,所有无法通过之前的递推进行转移
- 取模后变成 0,不会对结果产生影响,直接
continue
即可
- 再将当前行的某种填色方案与前一行的填色方案状态进行累加即可
即这个状态压缩递推就是:
- 先枚举行
- 再枚举该行的所有填色方案
- 该行当前的填色方案是否合法
- 若合法则检查前一行是否会与当前行的填色方案是否产生冲突
- 最后累加方案数即可
参见代码如下:
// 执行用时 :392 ms, 在所有 C++ 提交中击败了100.00%的用户
// 内存消耗 :8.4 MB, 在所有 C++ 提交中击败了100.00%的用户
#define LL long long
const LL MOD = 1e9+7;
LL dp[5050][65];
class Solution {
public:
int get(int v, int c){
return (v >> (c * 2)) % 4;
}
int numOfWays(int n) {
for (int s = 0; s < 64; s++){
int a = get(s, 0), b = get(s, 1), c = get(s, 2);
dp[1][s] = 0;
if (a == 3 || b == 3 || c == 3) continue;
if (a == b || b == c) continue;
dp[1][s] = 1;
}
for (int i = 2; i <= n; i++) {
for (int cur = 0; cur < 64; cur++) {
dp[i][cur] = 0;
int na = get(cur, 0), nb = get(cur, 1), nc = get(cur, 2);
if (na == 3 || nb == 3 || nc == 3) continue;
if (na == nb || nb == nc) continue;
for (int prev = 0; prev < 64; prev++) {
int pa = get(prev, 0), pb = get(prev, 1), pc = get(prev, 2);
if (pa == na || pb == nb || pc == nc) continue;
if (dp[i - 1][prev] == 0) continue;
dp[i][cur] = (dp[i][cur] + dp[i - 1][prev] ) % MOD;
}
}
}
LL ans = 0;
for (int s = 0; s < 64; s++){
ans = (ans + dp[n][s]) % MOD;
}
return ans;
}
};
注释版。
参见代码如下:
#define LL long long
const LL MOD = 1e9+7;
LL dp[5050][65];
// dp[i][bits] 已经填色到第i行,i行填色方案为bits的方案数
// bits为6位二进制,将其切割为三个[0,4),将3废弃,利用0,1,2表示三种颜色
class Solution {
public:
int get(int v, int c){
return (v >> (c * 2)) % 4;
}
int numOfWays(int n) {
// 初始化i=1第一行
for (int s = 0; s < 64; s++){
int a = get(s, 0), b = get(s, 1), c = get(s, 2);
dp[1][s] = 0;
// 3不在颜色种类内
if (a == 3 || b == 3 || c == 3) continue;
// 相邻的颜色不能相等
if (a == b || b == c) continue;
dp[1][s] = 1;
}
// 递推计算剩下的所有行
for (int i = 2; i <= n; i++) {
for (int cur = 0; cur < 64; cur++) {
// 当前行填cur方法
dp[i][cur] = 0;
int na = get(cur, 0), nb = get(cur, 1), nc = get(cur, 2);
if (na == 3 || nb == 3 || nc == 3) continue;
if (na == nb || nb == nc) continue;
// 当前行填cur合法,查看上一行颜色是否有冲突
for (int prev = 0; prev < 64; prev++) {
int pa = get(prev, 0), pb = get(prev, 1), pc = get(prev, 2);
if (pa == na || pb == nb || pc == nc) continue;
// dp数组在i-1行方案为prev时方案数为0即不存在这种方案,在此有两种情况导致:
// 1. prev该方案不合法,所有无法通过之前的递推进行转移
// 2. 取模后变成0,不会产生影响,直接continue即可
if (dp[i - 1][prev] == 0) continue;
dp[i][cur] = (dp[i][cur] + dp[i - 1][prev] ) % MOD;
}
}
}
LL ans = 0;
for (int s = 0; s < 64; s++){
ans = (ans + dp[n][s]) % MOD;
}
return ans;
}
};
方法二:数学+巧妙解法
来自题解区 1 号大佬,tql
:数学解决非常快乐
在此我就抛 link
了不再多阐述什么了。
对于本题还可以进行矩阵乘法的优化,能将时间复杂度降低到 ,但也是太菜了,先挖个坑,待填。