P3193 [HNOI2008] GT考试 题解

之前学矩阵乘的时候做的题,当时因为不会kmp搜索一稀里糊涂过去了,现在填个坑。

头图

Logos
image


P3193 [HNOI2008] GT考试

题链:洛谷 题库

题目大意:

求有多少个长度为n的数字串的子串中不包含给出的长度为m位的串,范围 n<=1e9m<=20

思路:

首先考虑DP,令zl[i][j]为原串匹配到第i位,短串最多可以匹配到第j位的方案数。

那么显然答案为:

i=0m1zl[n][i]

状态转移方程为:

zl[i][j]=k=09zl[i1][p]

其中的p不一定是0或者j1,因为加入字符k后,会有三种情况产生:

  1. 与原串中的下一个字符匹配;
  2. 失配,无法与任何字符相匹配;
  3. 重新与原串的另一个前缀匹配。

那么上面的式子就无法支持我们完成之后的操作了,所以我们换一种写法。

dh[k][j]为一个匹配了长度为k的串,有多少种增加数字的方法,使得与原串匹配的长度变成j

状态转移方程为:

zl[i][j]=k=0m1zl[i][k]×dh[k][j]

由于我们知道原串,所以整个dh数组是固定的,我们可以预处理出这个数组。方法是用kmp求出next数组后,枚举匹配长度k和字符ch,暴力计算出能匹配到前缀的长度。

那么,由于这道题是矩阵乘法专题里的dh数组恒不变,显然能想到用矩阵乘法的相关知识来解决。

因为我们最后只需要第n行矩阵,所以我们把每一行zl[i][j]抽象成一行,m1列的矩阵hdl[i],可推导出hdl[i]=hdl[i1]yns,那么,hdl[n]=hdl[0]ynsn

矩阵快速幂求出ynsn,再用矩阵乘法使其与hdl相乘,即可得出最终矩阵,再把答案一加一模就ok啦。

code:

#include<bits/stdc++.h>
#define fo(x,y,z) for(int (x)=(y);(x)<=(z);(x)++)
#define fu(x,y,z) for(int (x)=(y);(x)>=(z);(x)--)
typedef long long ll;
namespace Aventurine
{
	inline int qr()
	{
		char ch=getchar();int x=0,f=1;
		for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
		for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
		return x*f;
	}
	inline void qw(int x)
	{
		if(!x)
			return;
		qw(x/10);
		putchar(x%10+'0');
	}
	inline void qkg(int x)
	{
		if(x==0)
			putchar('0');
		else
			qw(x);
		putchar(' ');
	}
	inline void qhh(int x)
	{
		if(x==0)
			putchar('0');
		else
			qw(x);
		putchar('\n');
	}
}
#define qr qr()
using namespace std;
using namespace Aventurine;
const int Ratio=0;
const int N=55;
const int maxi=INT_MAX;
int n,len,mod,ans;
int kmp[N];
char s[N];
struct rmm
{
	int a[N][N];
	rmm()
	{//一定要初始化!一定要初始化!一定要初始化! 
		memset(a,0,sizeof a);
	}//在结构体中定义的数组需要初始化! 
}yns,hdl;
rmm operator*(rmm a,rmm b)//矩阵乘
{
	rmm c;
	fo(i,0,len-1)
		fo(j,0,len-1)
			fo(k,0,len-1)
				c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j]%mod+mod)%mod;
	return c;
}
rmm operator^(rmm a,int t)//矩阵快速幂
{
	rmm b;
	fo(i,0,len-1)
		b.a[i][i]=1;
	while(t)
	{
		if(t&1)
			b=b*a;
		a=a*a;
		t>>=1;
	}
	return b;
}
namespace Wisadel
{
	void Wprekmp()//kmp初始化
	{
		int j=0;
		fo(i,2,len)
		{
			while(j&&s[j+1]!=s[i])
				j=kmp[j];
			if(s[j+1]==s[i])
				j++;
			kmp[i]=j;
		}
	}
	void Wwork()
	{
		fo(i,0,len-1)
			for(char ch='0';ch<='9';ch++)
			{//枚举添加的字符 
				int j=i;
				while(j&&s[j+1]!=ch)
					j=kmp[j];
				if(s[j+1]==ch)
					j++;
				yns.a[i][j]=(yns.a[i][j]+1)%mod;
			}
		hdl.a[0][0]=1;//即为hdl[0] 
		yns=yns^n;
		hdl=hdl*yns;
		fo(i,0,len-1)
			ans=(ans+hdl.a[0][i])%mod;
	}
	short main()
	{
		n=qr,len=qr,mod=qr;
		scanf("%s",s+1);
		Wprekmp();
		Wwork();
		printf("%d\n",ans);
		return Ratio;
	}
}
int main(){return Wisadel::main();}

完结撒花
我放两张 “维什戴尔”,就把名字签这里,对吧?殿下告诉我,这个名字的意思是“许愿一个家”,但我从不许愿。啊?字太丑?还轮不到你来指指点点。

image
谁有W的好图啊aa球球了QwQ

image

posted @   Ratio_Y  阅读(70)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示