1. 题目来源
前导类似题:1411. 给 N x 3 网格图涂色的方案数
2. 题目解析
本周赛是商汤赞助的,第三题就是道 hard
题,且评论区有大佬说前导类似题:1411. 给 N x 3 网格图涂色的方案数是 2021 年 3 月 6 日阿里笔试原题,前导题可以推公式,也可以直接状压 dp
套路都可。
看到 m
这么小,应该想到是状压 dp
的,但是真的好长时间没做过 dp
了,还是被卡…
对列进行状压,状压方式为每个格子按照 3 进制记录其颜色,最多有 3*3*3*3*3=243
种情况,由于一列中颜色不能相同,所以情况远小于 243
,本列格子仅与上一列格子颜色有关,故需要状压记录每列格子颜色是什么即可。
状压dp:
- 状态定义:
f[i][s]
填好前i
列,且第i
列格子颜色状态为s
的方案数。 - 状态预处理: 一列中合法状态要求相邻格子颜色不能相同,故可以将一列中的合法格子状态先预处理出来。
- 初始化:
f[1][s]
第一列的格子可以随便填,s
为一列中所有的合法状态,也就是预处理出来的状态值,对应的方案数是 1。 - 状态转移: 先枚举第
i-1
列的状态值,再枚举第i
列的状态值,如果两个状态同位置颜色不同的话,说明可以合法转移,则累加方案数即可。 - 答案:
f[n][s]
,将所有的s
累加起来即可。
状压题非常套路,好久没写了手生了。
技巧:
本题对状态进行 3 进制压缩,并需要取出两个数的第 t
位判断是否相同,针对如何取出 k
进制的第 t
位有一个固定技巧:
- r e s = ( a % p o w ( k , t + 1 ) ) / p o w ( k , t ) res=(a\%pow(k, t+1))/pow(k,t) res=(a%pow(k,t+1))/pow(k,t)
- 取模
k
t
+
1
k^{t+1}
kt+1,会将
t
位之后的高位全部清 0。 - 除以
k
t
k^t
kt,会取出
t
位上的数值。
时间复杂度:
O
(
n
3
2
m
)
O(n3^{2m})
O(n32m)
空间复杂度:
O
(
n
3
2
m
)
O(n3^{2m})
O(n32m)
预处理当前状态能走到其余的所有合法状态,很不错的代码结构。当然也可以直接暴力枚举所有状态再判断也行。
typedef long long LL;
const int N = 1e3+5, M = 250;
const int MOD = 1e9+7;
int f[N][M];
bool st[M]; // 合法状态
vector<int> edge[M]; // 每种状态的合法转移状态
int DIG[7] = {1, 3, 9, 27, 81, 243, 729};
class Solution {
public:
// 取出 3 进制的第 k 位
int get(int state, int k) { return (state % DIG[k + 1]) / DIG[k]; }
int colorTheGrid(int m, int n) {
for (int i = 0; i <= n; i ++ )
for (int j = 0; j < M; j ++ )
f[i][j] = 0;
int lim = 1;
for (int i = 0; i < m; i ++ ) lim *= 3;
// 预处理:枚举一列的合法情况
for (int s = 0; s < lim; s ++ ) {
bool flag = true;
for (int i = 1; i < m; i ++ )
if (get(s, i - 1) == get(s, i)) flag = false;
st[s] = flag;
edge[s].clear();
}
// 预处理:每种状态的全部合法情况
for (int s = 0; s < lim; s ++ ) {
if (!st[s]) continue;
for (int ns = 0; ns < lim; ns ++ ) {
if (!st[ns]) continue;
bool flag = true;
for (int i = 0; i < m; i ++ )
if (get(s, i) == get(ns, i)) flag = false;
if (flag) edge[s].push_back(ns);
}
}
// 初始化
for (int s = 0; s < lim; s ++ ) if (st[s]) f[1][s] = 1;
for (int i = 2; i <= n; i ++ )
for (int s = 0; s < lim; s ++ ) {
for (int ns : edge[s])
f[i][ns] = (f[i - 1][s] + f[i][ns]) % MOD;
}
int res = 0;
for (int s = 0; s < lim; s ++ ) res = (res + f[n][s]) % MOD;
return res;
}
};