P11030 『DABOI Round 1』Blessings Repeated題解

Hanggoash發表於2024-09-12

P11030 『DABOI Round 1』Blessings Repeated題解

【形式化題意】

給定一個正整數 \(k\) 和兩個字串 \(S,T\)

設字串 \(s\)\(k\) 個字串 \(S\) 首尾相接得到的字串,\(n=\vert s \vert , m=\vert T \vert\)

設答案集合 \(P=\{ (i_0,i_1,\dots,i_{m-1}) \mid 0\le i_0 < i_1 < \dots < i_{m-1} < n, \forall~0 \le j < m, s_{i_j}=T_j \}\),請求出 \(\vert P \vert \bmod 998244353\)

輸入格式

輸入共 \(3\) 行。

\(1\)\(1\) 個整數,表示 \(k\)

\(2\)\(1\) 個字串,表示 \(S\)

\(3\)\(1\) 個字串,表示 \(T\)

輸出格式

輸出共 \(1\)\(1\) 個整數,表示答案。

樣例 #1

樣例輸入 #1

2
stocyhorz
cyh

樣例輸出 #1

4

樣例 #2

樣例輸入 #2

4
c
ccc

樣例輸出 #2

4

提示

【樣例 1 解釋】

\(S\) 重複 \(2\) 次得到 \(\texttt{stocyhorzstocyhorz}\)

答案集合 \(P=\{(3,4,5),(3,4,14),(3,13,14),(12,13,14) \}\),因此 \(\vert P\vert=4\)


【資料範圍】

對於 \(100\%\) 的資料,\(0<k\le10^{18}\)\(0 < \vert S \vert \le 5 \times 10^3\)\(0 < \vert T \vert \le 10\),字串 \(S,T\) 均由小寫英文字母組成。

\(\text{Point}\) \(k\le\) \(\vert S\vert\le\) \(\vert T\vert\le\)
\(1\sim2\) \(10^{18}\) \(5 \times 10^3\) \(1\)
\(3\) \(1\) \(5 \times 10^3\) \(2\)
\(4\sim5\) \(100\) \(5 \times 10 ^3\) \(2\)
\(6\sim7\) \(1\) \(50\) \(4\)
\(8\sim10\) \(10\) \(5 \times 10^3\) \(10\)
\(11\sim20\) \(10^{18}\) \(5 \times 10^3\) \(10\)

賽時想法

想到了做一個 \(dp\) ,但是一上來就把階段丟掉了,導致把自己繞暈了,大概想的是定義 \(dp[i]\) 為:如果 \(S\) 的當前位置位在 \(T\) 中,那麼選完了這一位以後對應的方案數 ,\(i\) 代表 \(T\) 中對應的第 \(i\) 個字元。轉移的時候就統計字首和就行,但這樣的想法很容易被 \(hack\) 掉,還是我太菜了,很顯然的一個例子是 \(T=aaa\) ,當我們列舉到 \(S\) 中某位是 \(a\) 時,\(dp[2]\) 會從 \(dp[1]\) 進行轉移,因此會多統計出來一部分。

正解

重新定義 \(dp[i][j]\) 為考慮完 \(S\) 中前 \(i\) 個後,選完 \(T\) 串中第 \(j\) 個位置的方案數,那麼轉移方程也十分的明顯了。

\[dp[i][j] = \begin{cases} dp[i-1][j] ,& S[i]!=T[j]\\ dp[i-1][j]+dp[i-1][j-1],&S[i]==T[j]\\ \end{cases}\]

發現 \(k\) 的資料範圍尤其的大,基本上要麼就是推數學結論,要麼就是矩陣加速遞推,現在 \(dp\) 都寫出來了,這下不得不遞推了。

核心思想

和普通的矩陣加速不同(很大可能是我刷的題太少了),本題的 \(dp\) 是帶條件的,也就是說,在某一個迴圈內列舉的時候,有可能會有很多個不同的轉移矩陣。

但最重要的地方在於,這些轉移矩陣的出現是有規律的,如\((A_1 \cdot A_2\cdot...\cdot A_n)\cdot...\cdot(A_1 \cdot A_2\cdot...\cdot A_n)\) 一共有 \(k\) 個這樣的迴圈,那麼我們記每一個迴圈根據結合律得出的轉移矩陣是 \(Sup\) ,總的轉移矩陣就是 \(Sup^k\)

最後在初始 \(dp\) 矩陣右乘上一個 \(Sup^k\) 就好了。

容易犯錯的地方

注意 \(dp[0]\) 的初值應該賦為 \(1\) ,不然答案為 \(0\) 了。

然後還有就是,由於每一個迴圈節內的子轉移矩陣是不同的,所以累乘的方式應該根據 \(dp\) 是左乘還是右乘轉移矩陣有所變化,比如下面給出的程式碼是左乘轉移矩陣,那麼在預處理的時候也應該從左邊乘進來,類似於一個棧的結構。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=20;
struct Matrix
{
	int n,m;
	int a[N][N];
	Matrix()
	{
		memset(a,0,sizeof(a));
	}
	void reset()
	{
		memset(a,0,sizeof(a));
	}
	void print()
	{
		for(int i=0;i<=n;++i)
		{
			for(int j=0;j<=m;++j)
				printf("%lld ",a[i][j]);
			putchar('\n');
		}
	}
};
Matrix I(int n)
{
	Matrix tmp;
	tmp.n=tmp.m=n;
	for(register int i=0;i<=n;++i)tmp.a[i][i]=1;
	return tmp;
}
const int MOD=998244353;
Matrix operator *(Matrix a,Matrix b)
{
	Matrix tmp;
	tmp.n=a.n,tmp.m=b.m;
	for(register int i=0;i<=a.n;++i)
		for(register int j=0;j<=b.m;j++)
			for(register int k=0;k<=a.m;++k)
				tmp.a[i][j]+=a.a[i][k]*b.a[k][j]%MOD,tmp.a[i][j]%=MOD;					
	return tmp; 
}
inline Matrix power(Matrix a,int b)
{
	Matrix ans=I(a.n);
	while(b)
	{
		if(b&1)ans=ans*a;
		a=a*a;
		b>>=1;
	}
	return ans;
}
int k,n;
char s[50000],t[100];
Matrix Ans,sup;
vector<int> rec[200];
inline void pre()
{
	cin>>k;
	cin>>s>>t;
	n=strlen(t);
	for(int i=0;i<n;++i)rec[t[i]].push_back(i+1);
	sup=I(n);
	Matrix tmp; 
	for(register int i=0;i<strlen(s);++i)
	{
		tmp=I(n);
		for(int j=0;j<rec[s[i]].size();++j)
		{
			int pos=rec[s[i]][j];
			tmp.a[pos][pos-1]=1;
		}
		sup=tmp*sup;
	}
	Ans.n=n,Ans.m=1,Ans.a[0][1]=1;
}
signed main()
{
	pre();
	Ans=power(sup,k)*Ans;
	cout<<Ans.a[n][1];
	return 0;
 } 

相關文章