题目:
http://lightoj.com/volume_showproblem.php?problem=1268题意:
思路:
首先知道矩阵可以解决这样一类经典问题:
给定一个有向图,问从A点恰好走k步(允许重复经过边)到达B点的方案数mod p的值
把给定的图转为邻接矩阵,即A(i,j)=1当且仅当存在一条边i->j。令C=A*A,那么C(i,j)=ΣA(i,k)*A(k,j),实际上就等于从点i到点j恰好经过2条边的路径数(枚举k为中转点)。类似地,C*A的第i行第j列就表示从i到j经过3条边的路径数。同理,如果要求经过k步的路径数,我们只需要二分求出A^k即可。
那么我们就可以把这个字符串中的点转换成lens个节点,0节点表示起始节点,lens节点表示匹配到字符串最后一个节点。mat[i][j]就是从i节点到j节点,即已经匹配到i字符下面要匹配j字符可能的方法数。当匹配到lens就是包含这个字符串,那么只需要建立mat[lens][lens]的矩阵,每次乘积都是增加一个字符之后从i字符匹配到j字符的方法数。在n次乘积之后从0节点到其他每个节点的方法数之和也就是从起始到最后匹配到某个非最终节点的路径数之和。也就是答案数了。
然后对于起始矩阵的建立,可以借用kmp的next数组完成每个字符节点的转移,也就是判断当前字符匹配位置之后加某个字符能够最大匹配到的位置。
很好的矩阵理解题,还有对kmp的next数组的理解,矩阵的知识还是要多看看的,数学还是很重要。
代码:
//kopyh
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define MOD 1000000007
#define N 112
using namespace std;
int n,m,sum,res,flag;
void getNext(char *pre, int len, int *next)
{
int i = 0,j = -1;
next[0] = -1;
while(i < len)
{
if(j == -1 || pre[i] == pre[j])
{
i++,j++;
if(pre[i]!=pre[j]) next[i] = j;
else next[i] = next [j];
}
else j = next[j];
}
}
struct Matrix
{
unsigned ma[N][N];
int n,m;
};
Matrix matMul(Matrix m1, Matrix m2)
{
Matrix ans;
memset(ans.ma,0,sizeof(ans.ma));
for(int i=0;i<m1.n;i++)
for(int j=0;j<m2.m;j++)
for(int k=0;k<m1.m;k++)
ans.ma[i][j]=(ans.ma[i][j]+m1.ma[i][k]*m2.ma[k][j]);
ans.n=m1.n; ans.m=m2.m;
return ans;
}
Matrix matPow(Matrix m1, int k)
{
Matrix ans;
for(int i=0;i<m1.n;i++)
for(int j=0;j<m1.m;j++)
ans.ma[i][j] = (i==j);
ans.n=ans.m=m1.n;
while(k)
{
if(k&1)ans = matMul(ans,m1);
m1 = matMul(m1,m1);
k>>=1;
}
return ans;
}
char s[N],ss[N];
int a[N];
int main()
{
int i,j,k,cas,T,t,x,y,z;
scanf("%d",&T);
cas=0;
while(T--)
{
scanf("%d%s%s",&n,&s,&ss);
x=strlen(s);
y=strlen(ss);
getNext(ss,y,a);
Matrix ans;
memset(ans.ma,0,sizeof(ans.ma));
ans.n=ans.m=y;
for(i=0;i<y;i++)
for(j=0;j<x;j++)
{
t = i;
while(t && ss[t]!=s[j])t = a[t];
if(ss[t] == s[j])t++;
ans.ma[i][t]++;
}
ans = matPow(ans,n);
unsigned res=0;
for(i=0;i<y;i++)res+=ans.ma[0][i];
printf("Case %d: %u\n",++cas,res);
}
return 0;
}