「一本通 6.5 练习 2」GT 考试

本文介绍了一个GT考试的编程题,使用动态规划、KMP算法和矩阵快速幂解决子串匹配问题。通过枚举n位数字中不包含特定子串的情况,计算满足条件的数字总数。文章详细分析了解题思路,包括状态转移方程和优化技巧,最终给出完整的C++代码实现。

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

「一本通 6.5 练习 2」GT 考试

题目大意

给你一个n,你需要枚举n位的数字,其中不能出现子串=t,问你这样的数字可以有多少个,最后的答案mod p

分析

这个题很容易想到dp, dp[i][j]dp[i][j]dp[i][j]代表n位数字代表的文本串s前i位的后j位与模式串t的前j位匹配上的方案数.

那我们最后的答案就是∑j=0m−1dp[n][j]\sum_{j=0}^{m-1}{dp[n][j]}j=0m1dp[n][j]

分析状态转移:dp[i][j]dp[i][j]dp[i][j]的状态转移到下一个状态的情况可能有

  • dp[i+1][k](0&lt;=k&lt;=j)dp[i+1][k] (0&lt;=k&lt;=j)dp[i+1][k](0<=k<=j) 文本串的第i+1位与模式串的j+1位没有匹配上,所以能匹配的位置跳到了模式串前面具有相同前缀的地方(kmp)

  • dp[i+1][j+1]dp[i+1][j+1]dp[i+1][j+1] 文本串的第i+1位与模式串的j+1位匹配上了

换句话来说,dp[i][j]dp[i][j]dp[i][j]的状态来源可能是i-1位匹配成功(dp[i−1][j−1]dp[i-1][j-1]dp[i1][j1]) 或者i-1匹配到某个位置k (dp[i−1][k]dp[i-1][k]dp[i1][k]) i位置失配,然后跳回了j

而且这个跳是与i无关的,可以利用这个性质降低复杂度. 定义二维数组b[i][j]b[i][j]b[i][j]。含义是假如现在前i位匹配成功了, 后面再加上一个数字进行匹配, 会变成了匹配到第j位,这里的的b[i][j]b[i][j]b[i][j]求法类似于nxt求法(多综合了一种情况, 这里的i<j是存在的,即j位匹配成功了,j+1位又匹配成功了, 那么b[j][j+1]++b[j][j+1]++b[j][j+1]++)

那么dp[i][j]=∑k=0m−1dp[i−1][k]∗b[k][j]dp[i][j]=\sum_{k=0}^{m-1}{dp[i-1][k]*b[k][j]}dp[i][j]=k=0m1dp[i1][k]b[k][j]

这里的乘积和运算与矩阵的运算是类似的,循环范围不变, 后面乘数不变, 可以先计算后面n个b[k][j]b[k][j]b[k][j]得到 ans矩阵, 再初始状态*ans得到答案,

//dp+kmp+矩阵快速幂
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;
#define ll long long
#define ull unsigned long long
#define pi acos(-1)
const int inf=0x3f3f3f3f;
const int N=505;
const int length=25;
ll n,m,p;
char s[N];
int nxt[N];
struct node{
    ll matrix[length+5][length+5]={0};
    node(int x=0){
        for(int i=0;i<length;i++){
            matrix[i][i]=x;
        }
    }
};
node mul(node a,node b){
    node ans;
    for(int i=0;i<m;i++){
        for(int j=0;j<m;j++){
            for(int k=0;k<m;k++){
                ans.matrix[i][j]=(ans.matrix[i][j]+a.matrix[i][k]*b.matrix[k][j]%p)%p;
            }
        }
    }
    return ans;
}
node pow(node a,ll k){
    node ans(1);
    while(k){
        if(k&1){
            ans=mul(ans,a);
        }
        k>>=1;
        a=mul(a,a);
    }
    return ans;
}
int main() 
{
#ifdef __DEBUG__
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
#endif
	scanf("%lld%lld%lld",&n,&m,&p);
	scanf("%s",s+1);
	node ans,base;
	nxt[1]=0;
	int pre=0,slen=strlen(s+1);
	for(int i=2;i<=slen;i++){//kmp求最大匹配长度
		while(pre&&s[pre+1]!=s[i])pre=nxt[pre];
		if(s[pre+1]==s[i]){
			++pre;
		}
		nxt[i]=pre;
	}
	for(int i=0;i<m;i++){
		for(int j=0;j<=9;j++){//枚举可能出现的任意数字
			pre=i;//这里的赋值导致了i<j可行
			while(pre&&s[pre+1]!=j+'0')pre=nxt[pre];
			if(s[pre+1]==j+'0'){
				++pre;
			}
			if(i!=m){//去掉非法状态,我们不需要全部能匹配上的子串
				base.matrix[i][pre]=(base.matrix[i][pre]+1)%p;
			}//i跳到pre 的方案数++
		}
	}
	ans.matrix[0][0]=1;//初始化条件
	ans=mul(ans,pow(base,n));//初始状态*矩阵快速幂的结果
	ll sum=0;
	for(int i=0;i<m;i++){//循环到m-1
		sum=(sum+ans.matrix[0][i])%p;
	}
	printf("%lld\n",sum);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值