演算法
狀態定義
最初顯然可以想到
\(f[i][j][k]\) 表示 \(A\) 串前 \(i\) 個, \(B\) 串前 \(j\) 個, 分割了 \(k\) 個子串
但是這樣無法遞推 \(k\) 維
於是加上一位 \(f[i][j][k][0/1]\), 最後一維表示是否選擇 \(A\) 子串當前這一位, 也就可以遞推的計算
狀態轉移
-
當前位置不使用
\(f[i][j][k][0]=f[i-1][j][k][0]+f[i-1][j][k][1]\), 顯然 -
當前位置使用
\(f[i][j][k][1]=f[i-1][j-1][k-1][0]+f[i-1][j-1][k][1]+f[i-1][j-1][k-1][1] \leftarrow (a[i]==b[j])\)
邊界條件
-
對於A子串前i位,匹配B串第1個字元,那麼只可能使用1個字串,這種情況如果第i位不進行匹配,那麼方案數就是之前所有能與第1位匹配的字元,所以 \(f[i][1][1][0]=sum(a[1~i-1]==b[1])\)
-
同上,但如果B串第一位和A串某一位匹配了話,那麼 \(f[i][1][1][1]=1\) 是顯然的
答案
即為 \(f[n][m][k][0/1]\)
空間複雜度最佳化
一眼滾動陣列
for (int i=1;i<=n;i++){
swap(now,pre); //交換,只要i改變
f[now][1][1][0]=s;//邊界1
if (a[i]==b[1]) f[now][1][1][1]=1,s++;//邊界2,解決了s統計的問題
for (int j=2;j<=m;j++){
for (int k=1;k<=t;k++){
if (a[i]==b[j]){
f[now][j][k][1]=((f[pre][j-1][k-1][1]+f[pre][j-1][k][1])%prime+f[pre][j-1][k-1][0])%prime;
}//方程2
f[now][j][k][0]=(f[pre][j][k][0]+f[pre][j][k][1])%prime;//方程1
}
}
for(int j=1;j<=m;j++)
for(int k=1;k<=t;k++)
f[pre][j][k][1]=f[pre][j][k][0]=0;//清空
}
總結
狀態的設計可以從需要遞推的項中找靈感
注意初始值一般為一行或一個點