题解 P11030 『DABOI Round 1』Blessings Repeated
前言
出题人题解。
原题目名叫 Nice God's mOther marIa(NGOI),但是为了比赛题目命名格式统一就改了,也算是个一语双关。
灵感来自对圣母名字的字符串进行一通操作,本来出的题太简单了,加强了一下就成了现在这个样子。
难度感觉也就绿蓝的样子。
题意
参见形式化题意。
分析
令 \(n = \vert S \vert , m = \vert T \vert\)。
测试点 \(1,2\)
性质 \(m \le 1\),即字符串 \(T\) 只有一个字符。
问题就转化为了 \(k\) 个首尾相接的字符串 \(S\) 中出现了多少个 \(T\) 字符。
于是直接统计 \(S\) 中出现了多少次,答案乘 \(k\) 即可,时间复杂度 \(O(n)\)。
k=int(input())
s=input()
t=input()
print(s.count(t)*k%998244353)
测试点 \(3\)
性质 \(k=1\),直接枚举两边字符即可,时间复杂度 \(O(n ^ 2)\)。
//the code is from chenjh
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
const int mod=998244353;
int n,m;
LL k;
char s[5005],t[11];
int main(){
scanf("%lld %s %s",&k,s,t),n=strlen(s),m=strlen(t);
int ans=0;
for(int i=0;i<n;i++)for(int j=i+1;j<n;j++)
(ans+=s[i]==t[0]&&s[j]==t[1])>=mod&&(ans-=mod);
printf("%d\n",ans);
return 0;
}
测试点 \(4,5\)
和上一部分不同之处在于 \(k\) 的范围扩大到了 \(k \le 100\),也就是说 \(k n \le 5 \times 10^5\)。
统计满足的二元组数量,只需要枚举一维,快速算另一维即可。
前缀和统计一下 \(T_0\) 的数量,遇到 \(T_1\) 相加一下即可,时间复杂度 \(O(k n)\)。
//the code is from chenjh
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
const int mod=998244353;
int n,m;
LL k;
char s[5005],t[11];
int main(){
scanf("%lld %s %s",&k,s,t),n=strlen(s),m=strlen(t);
int ans=0,w=0;
while(k--)for(int i=0;i<n;i++) s[i]==t[1]&&(ans+=w)>=mod&&(ans-=mod),w+=s[i]==t[0];
printf("%d\n",ans);
return 0;
}
测试点 \(6,7\)
根据测试点 \(3\) 的方式直接枚举判断即可,时间复杂度 \(O(n ^ {m})\),代码过于简单就不放了。
测试点 \(8,9,10\)
各种数据范围都相比前面的有所加强,所以不能暴力枚举了,考虑动态规划。
状态设计
设 \(f_{i,j}\) 表示字符串 \(s\) 前 \(i\) 个字符中 \(T\) 前 \(j\) 个字符组成的字符串出现的子序列方案数。
初始状态
状态转移
答案即 \(f_{k n,m}\)。
时间复杂度 \(O(k n m)\),空间复杂度可以滚动优化到 \(O(m)\)。
//the code is from chenjh
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
const int mod=998244353;
int n,m;
LL k;
char s[5005],t[11];
int f[11];
int main(){
scanf("%lld %s %s",&k,s,t),n=strlen(s),m=strlen(t);
*f=1;
while(k--)for(int i=0;i<n;i++)for(int j=m;j>0;--j)s[i]==t[j-1]&&(f[j]+=f[j-1])>=mod&&(f[j]-=mod);
printf("%d\n",f[m]);
return 0;
}
测试点 \(11 \sim 20\)
\(k \le 10^{18}\) 提示复杂度和 \(k\) 的一次无关,不难想到用矩阵乘法优化 DP。
对于每一个小写字母 \(x\) 构造一个 \((m+1) \times (m+1)\) 的矩阵 \(A_x\):
接着设矩阵 \(B=\prod\limits_{i=0}^{n-1}A_{S_i}\)。
构造 \(F\) 矩阵:
最后将 \(F \gets F \times B^k\) 即可,答案即为 \(F_m\)。
时间复杂度 \(O(n m ^3)\),其中矩阵快速幂的时间复杂度为 \(O(\log k m ^3)\)。
代码
//the code is from chenjh
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
const int mod=998244353;
template<typename T>
struct Mat{//封装矩阵类。
int n,m;
T **a;
Mat(int _n=0,int _m=0):n(_n),m(_m){
a=new T*[n];
for(int i=0;i<n;i++) a[i]=new T[m],memset(a[i],0,sizeof(T)*m);
}
Mat(const Mat&B){
n=B.n,m=B.m;
a=new T*[n];
for(int i=0;i<n;i++) a[i]=new T[m],memcpy(a[i],B.a[i],sizeof(T)*m);
}
~Mat(){delete[] a;}
Mat&operator = (const Mat&B)&{
delete[] a;
n=B.n,m=B.m;
a=new T*[n];
for(int i=0;i<n;i++) a[i]=new T[m],memcpy(a[i],B.a[i],sizeof(T)*m);
return *this;
}
Mat operator * (const Mat&B)const{//矩阵乘法。
Mat ret(n,B.m);
for(int i=0;i<n;++i)for(int j=0;j<B.m;++j)for(int k=0;k<m;++k)ret.a[i][j]=(ret.a[i][j]+(LL)a[i][k]*B.a[k][j]%mod)%mod;
return ret;
}
Mat&operator *= (const Mat&B)&{return *this=*this*B;}
};
LL k;
int n,m;
char s[5005],t[15];
int main(){
scanf("%lld %s %s",&k,s,t),n=strlen(s),m=strlen(t);
Mat<int> F(1,m+1),A[26],B(m+1,m+1);
for(int j=0;j<=m;j++) B.a[j][j]=1;
**F.a=1;
for(int i=0;i<26;i++){
A[i]=Mat<int>(m+1,m+1);
for(int j=0;j<=m;j++) A[i].a[j][j]=1;//对于每一个字母构造单位矩阵。
}
for(int i=0;i<m;i++) A[t[i]-'a'].a[i][i+1]=1;//将该转移位置的值为 1。
for(int i=0;i<n;i++) B*=A[s[i]-'a'];
for(;k;k>>=1,B*=B)if(k&1)F*=B;//矩阵快速幂
printf("%d\n",F.a[0][m]);
return 0;
}
据说还有一些其他的做法,但是我不会,欢迎大家提出自己的不同意见!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】