[HNOI2008] GT考试 题解

前言

  • 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长度,可以从如下转移
      1. \(j(j<m) -> k\)
      1. \(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]}\)

注意的点

  1. 算g的时候要注意与传统kmp的区别,传统中j是从0开始,但是这题要从i开始,并且对于传统kmp,你需要枚举'0' ~ '9',最后,算g的时候要记得j必须小于m
  2. 算ksm时记得把你记答案的矩阵初始化一下
  3. f记得初始化
  4. 内存都是按m开
  5. 检查循环的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,真的
posted @ 2026-06-23 11:27  msjing  阅读(7)  评论(0)    收藏  举报