Luogu3193 HNOI2008 GT考试

怎么这么神仙的题目呀

Description

link

\(n\) 位数,求其中不含有长度为 \(m\) 的一个数字串的方案数

\(n\le 10^9,m\le 20\)

Solution

一开始想到了正难则反,但是发现两边重合之后容斥的重复情况不会推……

之后想到了一个 \(dp\)

\(f_{i,j}\) 为前 \(i\) 位数,有前 \(j\) 的后一个串的方案数

最后答案是 \(\sum\limits^{m-1}_{i=0} f_{n,i}\)

然后的转移?

这个匹配长度好像还是不可处理

(看题解ing)

发现可以用 \(kmp\) 算转移的方法

然后就又会了一点:

\[f_{i,j}=\sum^{n}_ {k=1} f_{i-1,k}\times g_{k,j} \]

\(g[i][j]\) 就是 长度转移方案数

计算方法:

		for(int i=2,j=0;i<=m;++i)
		{
			while(j&&s[i]!=s[j+1]) j=nxt[j];
			if(s[i]==s[j+1]) ++j; nxt[i]=j;
		}
		for(int i=0;i<m;++i)
		{
			for(int j='0';j<='9';++j)
			{
				int k=i;
				while(k&&s[k+1]!=j) k=nxt[k];
				if(s[k+1]==j) ++k;
				++g[i+1][k+1]; 
			}
		}

之后这个 \(n\) 的范围?

还是不大行

(我不知为啥能想到矩乘,怕是面向数据范围编程)

这里的优化还是挺妙的:

\(dp\) 式子是个矩乘的式子对吧……

\(F[0]\) 表示 \(i\) 位数的矩阵,就是由所有 \(f_{i,j}\) 构成的

这个就变成了 \(F_i=F_{i-1}\times G\)

\(G\) 就是 \(g_{i,j}\) 是固定的

同时我们发现这里的 \(F_0\) 的第一位是个 \(1\)

(这里好像可以当做一个单位矩阵)

还不用再处理一下,就没了

\(kmp+dp\) 矩阵快速幂优化还是挺厉害的

\[ans=\sum_{i=0}^{n-1} F[n]_{1,i} \]

如果不理解请先学习矩阵加速数列(蒟蒻看了30min的加速数列就懂了这题)

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
namespace yspm{
	inline int read()
	{
		int res=0,f=1; char k;
		while(!isdigit(k=getchar())) if(k=='-') f=-1;
		while(isdigit(k)) res=res*10+k-'0',k=getchar();
		return res*f;
	}
	const int N=30;
	int n,m,mod,nxt[N],ans;
	char s[N];
	struct mat{
		int a[N][N];
		int* operator [](int x){return a[x];}
		inline void init(){for(int i=1;i<=m;++i) a[i][i]=1; return ;}
	}g;
	inline mat mul(mat x,mat y)
	{
		mat ans; memset(ans.a,0,sizeof(ans.a));
		for(int i=1;i<=m;++i)
		{
			for(int j=1;j<=m;++j)
			{
				for(int k=1;k<=m;++k)
				{
					ans[i][j]+=x[i][k]*y[k][j]; 
				}
			}
		}
		for(int i=1;i<=m;++i) for(int j=1;j<=m;++j) ans[i][j]%=mod;
		return ans;
	}
	inline mat ksm(mat x,int y)
	{
		mat ans; ans.init();
		for(;y;y>>=1,x=mul(x,x)) if(y&1) ans=mul(ans,x);
		return ans;
	}
	signed main()
	{
		n=read(); m=read(); mod=read();
		scanf("%s",s+1); nxt[0]=1;
		for(int i=2,j=0;i<=m;++i)
		{
			while(j&&s[i]!=s[j+1]) j=nxt[j];
			if(s[i]==s[j+1]) ++j; nxt[i]=j;
		}
		for(int i=0;i<m;++i)
		{
			for(int j='0';j<='9';++j)
			{
				int k=i;
				while(k&&s[k+1]!=j) k=nxt[k];
				if(s[k+1]==j) ++k;
				++g[i+1][k+1]; 
			}
		}
		g=ksm(g,n);
		for(int i=1;i<=m;++i) ans+=g[1][i],ans%=mod;
		printf("%lld\n",ans);
		return 0;
	}
}
signed main(){return yspm::main();}

猜你喜欢

转载自www.cnblogs.com/yspm/p/12944687.html
今日推荐