[H状压dp] lc1931. 用三种不同颜色为网格涂色(状压dp+技巧+好题+面试常考+周赛249_3)

1. 题目来源

链接:1931. 用三种不同颜色为网格涂色

前导类似题: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;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值