CCF CSP 202006-4 1246 (100分详细题解,矩阵乘法+快速幂)

本文介绍了一种通过矩阵乘法和快速幂算法解决CSP问题的方法,针对给定的数字串,通过状态转移规则构建转移矩阵,然后利用矩阵快速幂计算最终结果,处理了部分数字串的特殊情况,如首位数字的选择和补充。
摘要由CSDN通过智能技术生成

202006-4 1246 (100分详细题解,矩阵乘法+快速幂)

在这里插入图片描述

可以先看下csp官方的思路讲解,大思路是状态转移,先看下面s<=2的情况

1 -> 2
2 -> 4
4 -> 16
6 -> 6 64 4
16 -> 26(不考虑26464的原因是,16可以拆分为16,而1->2 , 6-> 64, 6, 4,去除冗余解)
... 详细见尾部代码

s<=2共有14个数字,故可以将这个14个数字离散化到0~13,形成14*14转移矩阵,随后根据线性代数矩阵的结合律,这14个数字构成一个行向量a,可以使用矩阵快速幂,快速得到这14个数字出现的次数。由于向量a初始时为{1,0…0},故代码中直接由res矩阵的第0行充当。

当s>2时,可以进行倒推,如:464 上一次由 26操作而来。同时考虑到题目给的s是一个部分数字串,故可能无法直接倒推回s=2,故s在整个序列的情况,可能分为s前面是1,s前面是6,或者s前面不是1或6, 因为4->16 6->64,都会产生两位,所以s的首个数字可能是16中的6,以及64中的6,由于s只是其中的一部分,所以我们要在前面补上1和6。你可能回想为什么不在后面补1和6,其实也考虑了,不过在代码中用i + 1 == str.size()来解决了。

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
const int N = 14, MOD = 998244353;

int n;
string S;
int id[100];        // id[x]将x映射到0~13
vector<int> vers{
    1, 2, 4, 6, 16, 26, 41, 42, 44,
    46, 61, 62, 64, 66
};
// 上面的数字进行运算后可以得出下面的贡献,两位数字的运算后的数字只有一个
// 如16->26(不考虑2,6,4,64的原因是,16可以拆分为1和6,而1->2 , 6->64, 6, 4)
// 故两位数字运算后只转移到前一位数字幂的后一位和后一位数字幂的前一位

// 当s的长度大于2时,我们可以根据转移的规律,逆推到s只有两位的情况。
// s在整个序列的情况,可能分为s前面是1,s前面是6,或者s前面不是1或6.
// 因为4->16 6->64,都会产生两位,所以s的首个数字可能是16中的6,以及64中的6,由于s只是其中的一部分,所以我们要补上1和6
vector<vector<int>> g{
    {2}, {4}, {1, 6, 16}, {6, 4, 64}, {26},
    {46}, {62}, {64}, {61}, {66}, {42},
    {44}, {41}, {46}
};
int tr[N][N];       // tr的第j列是对ver[j]的贡献

void init() {
    memset(id, -1, sizeof id);
    for (int i = 0; i < N; i++) id[vers[i]] = i;        // 映射
    //求转移矩阵
    for (int i = 0; i < N; i++)
        for (auto x : g[i])
            tr[i][id[x]] ++;
}

void mul(int c[][N], int a[][N], int b[][N]) {
    static int tmp[N][N];
    memset(tmp, 0, sizeof tmp);
    for (int i = 0; i < N; i++)     // 行
        for (int j = 0; j < N; j++)     // 列
            for (int k = 0; k < N; k++)     // 行×列
                tmp[i][j] = (tmp[i][j] + (LL)a[i][k] * b[k][j]) % MOD;
    memcpy(c, tmp, sizeof tmp);
}

int qmi(int k, int id) {
    if (id == -1) return 0;
    int res[N][N] = { 0 }, w[N][N];
    memcpy(w, tr, sizeof w);
    res[0][0] = 1;      // 初始,使用res的首行充当0时刻的行向量

    while (k) {
        if (k & 1) mul(res, res, w); //res=res*w
        mul(w, w, w);   // w=w*w;
        k >>= 1;
    }
    return res[0][id];
}

string get(string str) {
    string res;
    for (int i = 0; i < str.size(); i++)
        if (str[i] == '2') res += '1';
        else if (str[i] == '4') res += '2';
        else if (str[i] == '1') {
            if (i + 1 == str.size() || str[i + 1] == '6') res += '4', i++;
            else return "";
        } else {
            if (i + 1 == str.size() || str[i + 1] == '4') res += '6', i++;
            else return "";
        }
    return res;
}

int dfs(int k, string& str) {
    if (str.size() <= 2) return qmi(k, id[stoi(str)]);
    int res = 0;
    for (string s : {"", "1", "6"}) {
        auto t = get(s + str);
        if (t.size()) res = (res + dfs(k - 1, t)) % MOD;        // !!!!!!!
    }
    return res;
}

int main() {
    init();
    cin >> n >> S;
    cout << dfs(n, S) << endl;
    return 0;
}
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值