CCF CSP 1246 100分题解(c++)

这篇博客探讨了一道涉及字符串序列转换的难题,其中涉及到快速幂运算和矩阵快速幂的应用。作者首先分析了数据特点,指出当字符串长度为1或2时可以直接枚举,然后构建了14x14的转移矩阵。通过矩阵快速幂解决小规模问题,并通过逆推处理字符串长度大于2的情况。代码中展示了如何实现这个算法,包括转移矩阵的初始化、矩阵乘法和快速幂运算。
摘要由CSDN通过智能技术生成

这道题难度挺大的,尤其是最后一个点。

首先观察数据点,前24个点数字串s的长度是1或2,

其次n特别大 ,肯定是要用到快速幂的,CCF CSP不止一次考过快速幂

当s的长度为1或2时,通过简单的排列组合知识也知道,s的情况并不多,我们可以直接枚举转移方程,转移方程如下。

显然我们需要矩阵快速幂。

当s的长度为1或2时,s的情况只有以上14种

我们构造转移矩阵的时候,可以考虑将这些点离散化,即只构造一个14*14的转移矩阵

按照这十四个离散的值从小到大,分别映射成0到13.

只要转移矩阵给出来了,套下矩阵快速幂的板子,前96分就到手了。

重点是最后四分。

根据y总的视频讲解,当s的长度大于2时,我们可以根据转移的规律,逆推到s只有两位的情况。

s是由什么转移过来的呢?

s在整个序列的情况,可能分为s前面是1,s前面是6,或者s前面不是1或6.

它对应的转移情况不尽相同

下列代码中的get的函数,是求s上一层是由什么转移过来的。

如果不存在转移到s的上一层,则返回空串。

如果存在,就递归查找,直到查找到上一层的长度是2.

LL dfs(int x,string&sub)

{

    if(sub.size()<=2)return qpow(x,id[stoi(sub)]);//如果长度小于等于2,直接矩阵快速幂求

    LL res=0;

    for(string s:{"","1","6"}){//

        string tmp=get(s+sub);

        if(tmp.size())res=(res+dfs(x-1,tmp))%mod;

    }

    return res;

}

完整代码如下

#include<iostream>

#include<vector>

#include<string>

#include<map>

#include<set>

#include<queue>

#include<algorithm>

#include<cstdio>

#include<cstring>

#define rep(i,start,end) for(int i=start;i<=end;i++)

using namespace std;

typedef long long LL;

typedef pair<LL,LL> pa;

const int mod=998244353;

int x;

string sub;

const int N=14;

int w[N][N];//转移矩阵

int id[100];

vector<int> v{1,2,4,6,16,26,41,42,44,46,61,62,64,66};

vector<vector<int>> vv{

    {2},{4},{1,6,16},{6,4,64},{26},{46},{62},{64},{61},{66},{42},{44},{41},{46}

};

void init()

{

    memset(id,-1,sizeof(id));

    for(int i=0;i<N;i++)

        id[v[i]]=i;

    //求出来转移矩阵

    for(int i=0;i<N;i++){

        for(auto t:vv[i])

            w[i][id[t]]++;

    }

}

void multi(int c[][N],int a[][N],int b[][N])

{

    //求c=a*b

    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 qpow(int x,int id)

{

    if(id==-1)return 0;//代表出现不可能的数

    int ans[N][N];

    int tr[N][N];//转移矩阵,因为可能不止一次使用w,所有不可以直接改变w的值

    memset(ans,0,sizeof(ans));

    for(int i=0;i<N;i++)ans[i][i]=1;//单位矩阵

    memcpy(tr,w,sizeof(w));

    while(x){

        if(x&1)multi(ans,ans,tr);

        multi(tr,tr,tr);

        x>>=1;

    }

    return ans[0][id];

}

string get(string str)

{

    string ans;

    int n=str.size();

    for(int i=0;i<n;i++){

        //遍历str的元素

        if(str[i]=='1'){

            if(i+1==n||str[i+1]=='6')ans+='4',i++;//16同时出现。代表上一轮是4

            else return "";

        }

        else if(str[i]=='2')ans+='1';

        else if(str[i]=='4')ans+='2';

        else{

            if(i+1==n||str[i+1]=='4')ans+='6',i++;

            else return "";

        }

    }

    return ans;

}

LL dfs(int x,string&sub)

{

    if(sub.size()<=2)return qpow(x,id[stoi(sub)]);//如果长度小于等于2

    LL res=0;

    for(string s:{"","1","6"}){//可能是完整的,也可能前面少个1 6

        string tmp=get(s+sub);

        if(tmp.size())res=(res+dfs(x-1,tmp))%mod;

    }

    return res;

}

int main()

{

    //freopen("in.txt","r",stdin);

    //freopen("out.txt","w",stdout);

    std::ios::sync_with_stdio(false);

    init();

    cin>>x>>sub;

    cout<<dfs(x,sub)<<endl;

}

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值