【矩阵的十大经典题目中的第九】

本文详细介绍使用矩阵状态转移解决复杂计数问题的方法,包括如何构造状态转移矩阵,以及通过枚举状态进行状态转移的过程。文章包含两个实例,分别是在矩阵上填黑块的方案计数和在序列中填数字避免特定模式的方案计数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Gym - 101635C Macarons
Step1 Problem:

给你 n*m 的矩阵,你可以填 1 * 1 的黑块,或者 1 * 2 的黑块,或者 2 * 1 的黑块。求有多少填的方案可以将矩阵填满。
数据范围:
1 <= n <= 8, 1 <= m <= 1e18.

Step2 Ideas:

感谢 dq 给我讲解了一波,发现了一篇很棒的博客
前置技能:你需要将经典题目 8 先学会了。
这是你就知道你需要构造一个状态转移矩阵。
我们可以枚举第 i 列所有黑块所能产生的样子,然后将第 i 列填满所构成的第 i+1 列的状态,就是所枚举的第 i 列状态能到达第 i+1 列状态。
核心注意点:所填黑块必须和第 i+1 列有关,不然该状态就不是我们所枚举想要的状态了。博客里面有讲。

Step3 Code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1<<8;
const int MOD = 1e9;
struct node
{
    ll a[N][N];
};
node c;
int n;
void dfs(int u, int now, int nex)
{
    if(u == n) {
        c.a[now][nex]++;//now 状态 能到达 nex 状态
        return ;
    }
    if(now>>u&1) {//当前位置有黑块
        dfs(u+1, now, nex);//不填
        dfs(u+1, now, nex|(1<<u));//填 1 * 1
        if(u+2<=n && (now&(1<<(u+1)))) dfs(u+2, now, (nex|(1<<u))|(1<<(u+1))); // 下一个位置也有黑块,可以直接对第 i+1 列填 2*1.
    }
    else {
        dfs(u+1, now, nex|(1<<u));//当前位置为空得填满,填 1*2.
    }
}
node mul(node x, node y)
{
    node ans;
    memset(ans.a, 0, sizeof(ans.a));
    int m = (1<<n);
    for(int k = 0; k < m; k++)
    {
        for(int i = 0; i < m; i++)
        {
            if(x.a[i][k])
            for(int j = 0; j < m; j++)
            {
                ans.a[i][j] += x.a[i][k]*y.a[k][j];
                ans.a[i][j] %= MOD;
            }
        }
    }
    return ans;
}
node Pow(node x, ll m)
{
    node ans;
    memset(ans.a, 0, sizeof(ans.a));
    for(int i = 0; i < (1<<n); i++) ans.a[i][i] = 1;
    while(m)
    {
        if(m&1) ans = mul(ans, x);
        x = mul(x, x);
        m >>= 1;
    }
    return ans;
}
int main()
{
    ll m;
    scanf("%d %lld", &n, &m);
    memset(c.a, 0, sizeof(c.a));
    for(int i = 0; i < (1<<n); i++)
        dfs(0, i, 0);
    node ans = Pow(c, m);
    printf("%lld\n", ans.a[(1<<n)-1][(1<<n)-1]);//输出状态全填到状态全填, m 列的方案数。
    return 0;
}

ACM-ICPC 2018 焦作赛区网络预赛 L. Poor God Water
Step1 Problem:

有 n 个位置,每个位置可以填 0, 1, 2.
相邻三个数不能满足如下几个形式:
0, 0, 0
1, 1, 1
2, 2, 2
2, 1, 2
2, 0, 2
1, 2, 0
0, 1, 2
问你填满 n 位的方案数。

Step2 Ideas:

构造状态转移矩阵
枚举两位的所有状态,多加一位如果合法,就得到了两位状态到两位状态的转移。

Step3 Code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 12;
const int MOD = 1e9+7;
struct node
{
    ll a[N][N];
};
node c, ans;
node mul(node x, node y)
{
    node ans;
    memset(ans.a, 0, sizeof(ans.a));
    for(int k = 0; k < 9; k++)
    {
        for(int i = 0; i < 9; i++)
        {
            if(x.a[i][k])
            for(int j = 0; j < 9; j++)
            {
                ans.a[i][j] += x.a[i][k]*y.a[k][j];
                ans.a[i][j] %= MOD;
            }
        }
    }
    return ans;
}
node Pow(node x, ll m)
{
    node ans;
    memset(ans.a, 0, sizeof(ans.a));
    for(int i = 0; i < 9; i++) ans.a[i][i] = 1;
    while(m)
    {
        if(m&1) ans = mul(ans, x);
        x = mul(x, x);
        m >>= 1;
    }
    return ans;
}
int ok[10] = {111, 222, 0, 120, 21, 212, 202};
void init()
{
    memset(c.a, 0, sizeof(c.a));
    for(int i = 0; i < 3; i++)
    {
        for(int j = 0; j < 3; j++)
        {
            for(int k = 0; k < 3; k++)
            {
                int t = i*100+j*10+k, k1;
                for(k1 = 0; k1 < 7; k1++) {
                    if(t == ok[k1]) break;
                }
                if(k1 == 7) c.a[i*3+j][j*3+k]++;//如果合法
            }
        }
    }
}
int main()
{
    int T;
    ll n;
    scanf("%d", &T);
    init();
    while(T--)
    {
        scanf("%lld", &n);
        if(n < 3) {
            if(n == 1) printf("3\n");
            else printf("9\n");
            continue;
        }
        ans = Pow(c, n-2);//矩阵是两位的状态到新的两位的状态,默认有两位了。
        ll Ans = 0;
        for(int i = 0; i < 9; i++)
        {
            for(int j = 0; j < 9; j++)
            {
                Ans += ans.a[i][j], Ans%=MOD;
            }
        }
        printf("%lld\n", Ans);//因为矩阵转移都是合法状态,所以是所有状态的和。
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值