[HNOI2008]GT考试
矩阵乘法加速动态规划
step1
首先先阐述一个sb错误:我刚开始以为给的序列是无关答案的,后来发现其实是不行的,因为例如
n=4 m=3 数列为101和数列为111时是不一样的答案因为对于1111 1101其一个有重复一个无重复构成的答案不同应该只有我这种蒟蒻会这么想吧
切入正题:
在这里设\(dp[i][j]\) 为文本串到i最多能匹配到模式串的j位上(文本串中i-j+1--i为与模式串1--j位相等)上的数总数
再引入一个数组\(g\),\(g[i][j]\)表示在模式串前缀为i后加一个数字使得后缀最多匹配前缀数量为j的方案数
答案即为\(\sum_{i=0}^{m-1}dp[n][i]\)
可以得出状态转移方程\(dp[i][j]=\sum_{k=0}^{m-1}{dp[i-1][k]+g[k][j]}\)
什么意思呢?其实就是在i位枚举0-9的可能,使i-1位上匹配的k个变为j个。
然后本问题就可以在\(O(n*m^2)\) 的时间内解决了
介绍一下\(g\)数组的求法,显然涉及到后缀前缀匹配,使用kmp即可。
void kmp()
{
p[1]=0;
for(int i=2;i<=m;i++)
{
int j=p[i-1];
while(s[j+1]!=s[i]&&j)j=p[j];
if(s[j+1]==s[i])p[i]=j+1;
}
for(int i=0;i<=m-1;i++)//在i+1位上放j可以使得最多匹配个数
for(int j='0';j<='9';j++)
{
int k=i;
while(s[k+1]!=j&&k)k=p[k];
if(s[k+1]==j)k++;
g[i][k]++;
}
}
step2
显然上文的复杂度是不足以通过的。
此时观察状态转移方程\(dp[i][j]=\sum_{k=0}^{m-1}{dp[i-1][k]+g[k][j]}\)
是不是很像矩阵乘法?
我们可以建立两个矩阵\(DP\)矩阵和\(G\) 矩阵
\[\begin{matrix}
1 \\
0 \\
0 \\
\cdots \\
0
\end{matrix}\tag{DP}
\]
\[\begin{matrix}
g[0][0] &g[0][1] &\cdots &g[0][m-1] \\
g[1][0] &\cdots & \cdots &g[1][m-1] \\
\cdots &\cdots &\cdots &\cdots \\
g[m-1][0] &g[m-1][1] &\cdots & g[m-1][m-1]
\end{matrix}\tag{G}
\]
此时发现\(DP\)矩阵表示\(i=0\)时的情况即\(dp[0][0]=1\)其余为0
把\(DP\)矩阵与\(G\)矩阵的n次相乘即得出最终答案了
#include<bits/stdc++.h>
using namespace std;
int n,m,mo,tans;
char s[25];
int p[25],g[25][25];
void kmp()
{
p[1]=0;
for(int i=2;i<=m;i++)
{
int j=p[i-1];
while(s[j+1]!=s[i]&&j)j=p[j];
if(s[j+1]==s[i])p[i]=j+1;
}
for(int i=0;i<=m-1;i++)
for(int j='0';j<='9';j++)
{
int k=i;
while(s[k+1]!=j&&k)k=p[k];
if(s[k+1]==j)k++;
g[i][k]++;
}
}
struct node{
int p[25][25];
node(){memset(p,0,sizeof(p));}
}ans,G,dp;
void tim(node &a,node b)
{
node c;
for(int k=0;k<=m-1;k++)
for(int i=0;i<=m-1;i++)
for(int j=0;j<=m-1;j++)
c.p[i][j]=(c.p[i][j]+a.p[i][k]*b.p[k][j])%mo;
a=c;
}
void pow(node &G)
{
int t=n;for(int i=0;i<=m-1;i++)ans.p[i][i]=1;
while(t!=1)
{
if(t%2)tim(ans,G);
t/=2;tim(G,G);
}
tim(ans,G);
G=ans;
}
int main()
{
scanf("%d%d%d",&n,&m,&mo);
cin>>s+1;kmp();dp.p[0][0]=1;
for(int i=0;i<=m-1;i++)
for(int j=0;j<=m-1;j++)
G.p[i][j]=g[i][j];
pow(G);tim(dp,G);
for(int i=0;i<=m-1;i++)tans=(tans+dp.p[0][i])%mo;
printf("%d",tans);
return 0;
}