前言
- woc好难啊这是啥啊
- 上一次huge这么详细讲的还是P10936 导弹防御塔
- 大概理解了写题解吧
其实真要是考试遇到了就拿滚木就行了
solve
- 石山大暴力就不打了
因为msjing的代码能力非常蒻
- 我们通过大
多量贺刷题题解,看到是统计方案数,尝试dp
woc咋想到的dp
- 设计状态,\(f[i][j]\)表示对于构造的串的前缀i位可以与模式串的后缀j位匹配上的合法方案数
- 为什么不用考虑整串而是只用考虑后缀呢?即为什么方案数是正确的?
- 关键就在合法
- 我们的递推是从前面推过来的,如果前面有一个是不合法的,那么就不符合dp定义了,所以就不需要考虑前面的问题了
- 行开始搞dp
- 由于要求合法的,所以匹配长度必须\(<m\),我们要转移到n,因此转移方程就能写了
- \(\sum\limits_{i=0}^{i<m}{f[n][i]}\)
- 我们发现n是\(10^9\),炸炸?!
- 所以我们并不能对这个优化了
- 考虑一下转化,我们发现模式串是固定的,考虑从这里下手
- 对于\(i-1 -> i\),如下转移

- 对于一个k长度,可以从如下转移
-
- \(j(j<m) -> k\)
-
- \(j(j>m) -> k\)(方案数减少了)
- 对于k长度的匹配,可以用kmp/暴力处理出从\(k -> j\)的转移的方案数,记为g,对于\(i-1 -> i\),分布计数原理,得到:
- \(f[i][j] = \sum\limits_{k=0}^{k<m}{f[i-1][k]*g[k][j]}\)
- 搞完这个,你就可以打出一个比较朴素的解法了,但是还是没有解决上面的问题
- 怎么办呢
- 发现这个转移是不断乘g的,考虑采用矩阵的方法
- 转化,发现需要求\(g^n\),ksm搞一下
- 转移的f就可以转化成行向量,其中\(f[0] = 1\),其余都是0,在上面提到的朴素解法的初始化里更好理解
- 要做的是算\(f \times g^n\)
- 答案统计就是\(\sum\limits_{i=0}^{i<m}{f[i]}\)
注意的点
- 算g的时候要注意与传统kmp的区别,传统中j是从0开始,但是这题要从i开始,并且对于传统kmp,你需要枚举
'0' ~ '9',最后,算g的时候要记得j必须小于m
- 算ksm时记得把你记答案的矩阵初始化一下
- f记得初始化
- 内存都是按m开
- 检查循环的k与你的模数变量名是否冲突
写在写代码前
- 实现就比较容易了,msjing这么一个代码能力为滚木的OIER还能从暴力一遍码过,码量不长倒是
请不要过度关注msjing为啥把三层循环压成一行了
code
#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
constexpr int maxn=110;
int n,m,p;
int f[maxn],g[maxn][maxn];
int nxt[maxn];
string got,s;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
void pre(int n)
{
for (int i=2,j=0;i<=n;i++)
{
while (j>0 && s[i]!=s[j+1]) j=nxt[j];
if (s[i] == s[j+1]) j++;
nxt[i]=j;
}
}
void kmp(int n)
{
for (int i=0;i<n;i++)
{
for (int ch='0';ch<='9';ch++)
{
int j=i;
while (j>0 && s[j+1]!=ch) j=nxt[j];
if (s[j+1] == ch) j++;
if (j<m) g[i][j]++;
}
}
}
int c[maxn],c_[maxn][maxn];
void multi1(int a[],int b[][maxn])
{
for (int k=0;k<m;k++) for (int j=0;j<m;j++) c[j]=(c[j]+a[k]*b[k][j])%p;
memcpy(a,c,sizeof(c));memset(c,0,sizeof(c));
}
void multi2(int a[][maxn],int b[][maxn])
{
for (int k=0;k<m;k++) for (int i=0;i<m;i++) for (int j=0;j<m;j++) c_[i][j]=(c_[i][j]+a[i][k]*b[k][j])%p;
memcpy(a,c_,sizeof(c_));memset(c_,0,sizeof(c_));
}
int as[maxn][maxn];
void ksm()
{
while (n)
{
if (n&1) multi2(as,g);
multi2(g,g);
n>>=1;
}
}
int main()
{
n=read();m=read();p=read();
cin >> got;s=" "+got;
pre(m);kmp(m);
f[0]=1;for (int i=0;i<m;i++) as[i][i]=1;
ksm();multi1(f,as);
int ans=0;
for (int i=0;i<m;i++) ans=(ans+f[i])%p;
cout << ans <<endl;
return 0;
}
写在最后
- sbmqwm认为这题还行,但是msjing认为这题太难想了应该上紫
- 大家要多思考这道题,多想多看,dp的设计还是很困难的,
毕竟铸币msjing在huge讲之前完全看不懂呢,虽说都是板板?!,但是kmp本身不好理解,再加上这神秘dp就更难搞了
- 讲的可能不够详细,多多包涵
- 东西有锅请评论,msjing会回来修的
- 没了喵
我真的不认识sbmqwm,真的