题面
题解
可以发现, S S S 和 T T T 相似,等价于它们的最长公共子序列长度至少为 n − k n - k n−k。
首先考虑如何求出两个字符串的 LCS \text{LCS} LCS(最长公共子序列)。考虑 dp:
设
f
i
,
j
f_{i,j}
fi,j 表示
S
[
1
∼
i
]
S[1\sim i]
S[1∼i] 与
T
[
1
∼
j
]
T[1\sim j]
T[1∼j] 的
LCS
\text{LCS}
LCS 长度,转移是显然的:
f
i
,
j
=
{
f
i
−
1
,
j
−
1
+
1
if
S
[
i
]
=
T
[
j
]
max
(
f
i
−
1
,
j
,
f
i
,
j
−
1
)
if
S
[
i
]
≠
T
[
j
]
\begin{aligned} f_{i,j}= \begin{cases} f_{i-1,j-1}+1& \operatorname{if }S[i]=T[j]\\ \max(f_{i-1,j},f_{i,j-1})& \operatorname{if }S[i]\neq T[j] \end{cases} \end{aligned}
fi,j={fi−1,j−1+1max(fi−1,j,fi,j−1)ifS[i]=T[j]ifS[i]=T[j]
然后考虑如何拓展到
T
T
T 任意的情况。自然的想法是 dp 套 dp:
设 d p [ j ] [ f 1 , j , f 2 , j , ⋯ , f n , j ] dp[j][f_{1,j},f_{2,j},\cdots,f_{n,j}] dp[j][f1,j,f2,j,⋯,fn,j] 表示只考虑 T T T 的前 j j j 位,那么这 j j j 位有多少种选取方式,使此时的 f ∗ , j f_{*,j} f∗,j 数组如 dp 下标所示。
这个状态设计显然是可以优化的,不难证得 f i + 1 , j − f i , j ∈ { 0 , 1 } f_{i+1,j}-f_{i,j}\in\{0,1\} fi+1,j−fi,j∈{0,1},又由于 f 0 , j = 0 f_{0,j}=0 f0,j=0,所以我们把 d p dp dp 数组的第二维变成差分数组 c i = f i + 1 − f i , j c_i=f_{i+1}-f_{i,j} ci=fi+1−fi,j 的 01 串状压即可。
考虑这个 dp 的时间复杂度,状态数是 O ( n 2 n ) O(n2^n) O(n2n) 的。直接暴力 DP 的时间复杂度就约为 O ( 2 n poly ( n ) ) O(2^n\operatorname{poly}(n)) O(2npoly(n))。考虑如何利用 k k k 很小这个性质进行优化。
首先考虑优化状态数。
考虑某个满足
∣
i
−
j
∣
>
k
|i-j|>k
∣i−j∣>k 的
f
i
,
j
f_{i,j}
fi,j(显然有
f
i
,
j
≤
min
(
i
,
j
)
f_{i,j}\leq \min(i,j)
fi,j≤min(i,j)),再考虑这个
f
i
,
j
f_{i,j}
fi,j 转移到
f
n
,
n
f_{n,n}
fn,n 时最大会变成多少:观察最上面给出的
f
i
,
j
f_{i,j}
fi,j 递推式,显然按第一种情况转移值才会变大,而第一种情况最多转移
min
(
n
−
i
,
n
−
j
)
\min(n-i,n-j)
min(n−i,n−j) 次,所以从
f
i
,
j
f_{i,j}
fi,j 转移到
f
n
,
n
f_{n,n}
fn,n 时的值不大于:
f
i
,
j
+
min
(
n
−
i
,
n
−
j
)
≤
min
(
i
,
j
)
+
min
(
n
−
i
,
n
−
j
)
=
n
−
(
max
(
i
,
j
)
−
min
(
i
,
j
)
)
<
n
−
k
f_{i,j}+\min(n-i,n-j)\leq \min(i,j)+\min(n-i,n-j)=n-(\max(i,j)-\min(i,j))<n-k
fi,j+min(n−i,n−j)≤min(i,j)+min(n−i,n−j)=n−(max(i,j)−min(i,j))<n−k
所以这个
f
i
,
j
f_{i,j}
fi,j 不可能使
f
n
,
n
≥
n
−
k
f_{n,n}\geq n-k
fn,n≥n−k。
于是,与其记录整个 f f f 数组,我们只需要记录 f j − k , j , f j − k + 1 , j , ⋯ , f j + k , j f_{j−k,j} , f_{j−k+1,j},\cdots, f_{j+k,j} fj−k,j,fj−k+1,j,⋯,fj+k,j,这可以通过一个长度为 2 k 2k 2k 的差分数组以及一个 f j , j f_{j,j} fj,j 得到。(这个差分数组也可以用 01 串状压表示)
又由于 f j , j f_{j,j} fj,j 的值必须介于 [ j − k , j ] [j − k, j] [j−k,j] 之间才能使 f n , n ≥ n − k f_{n,n}\geq n-k fn,n≥n−k,所以我们将 d p dp dp 数组的第二维换成一个数 x ∈ [ 0 , k ] x\in [0,k] x∈[0,k] 表示 f j , j = j − k + x f_{j,j}=j-k+x fj,j=j−k+x,第三维换成差分数组的 01 串状压。此时 d p dp dp 的总状态数只有 O ( n k 2 2 k ) O(nk2^{2k}) O(nk22k)。
再考虑优化转移。
转移的过程是这样的:对于 d p [ j ] [ x ] [ s t a ] dp[j][x][sta] dp[j][x][sta],枚举一个字符 c c c 为 T [ j + 1 ] T[j+1] T[j+1],向 d p [ j + 1 ] [ x ′ ] [ s t a ′ ] dp[j+1][x'][sta'] dp[j+1][x′][sta′] 转移。(其中 x ′ x' x′ 和 s t a ′ sta' sta′ 需要我们计算出来)
为了方便转移,我们可以先预处理一个 pair 数组 t u r n [ j ] [ x ] [ s t a ] [ m a t c h ] turn[j][x][sta][match] turn[j][x][sta][match],表示当 d p [ j ] [ x ] [ s t a ] dp[j][x][sta] dp[j][x][sta] 向 d p [ j + 1 ] [ x ′ ] [ s t a ′ ] dp[j+1][x'][sta'] dp[j+1][x′][sta′] 转移,枚举的字符 c c c 和 S [ j − k + 1 ] , S [ j − k + 2 ] , ⋯ , S [ j + k + 1 ] S[j-k+1],S[j-k+2],\cdots,S[j+k+1] S[j−k+1],S[j−k+2],⋯,S[j+k+1] 的匹配状态为 m a t c h match match 时, x ′ x' x′ 和 s t a ′ sta' sta′ 分别是多少。
也就是说,我们知道了 f j − k ∼ j + k , j f_{j-k\sim j+k,j} fj−k∼j+k,j 和 m a t c h match match,要求此时的 f j − k + 1 ∼ j + k + 1 , j + 1 f_{j-k+1\sim j+k+1,j+1} fj−k+1∼j+k+1,j+1。
按照最上面的递推式正常转移即可。
然后你会发现一个很神奇的东西:要求出 x x x 和 s t a sta sta 的变化量根本不需要 j j j,只需要 x x x 和 s t a sta sta!
所以 t u r n turn turn 的第一维 j j j 可以直接去掉。
所以才叫预处理
t
u
r
n
\sout{turn}
turn。
注意 m a t c h match match 也是可以预处理的,然后此时预处理的时间是 O ( k 2 2 2 k 2 2 k + 1 + 26 n k ) = O ( k 2 2 4 k + 1 + 26 n k ) O(k^22^{2k}2^{2k+1}+26nk)=O(k^22^{4k+1}+26nk) O(k222k22k+1+26nk)=O(k224k+1+26nk),可以接受。
转移的时间优化到了 O ( 26 n k 2 2 k ) O(26nk2^{2k}) O(26nk22k)。
其实已经能跑过了,但有点卡。
所以可以进一步的优化:
你发现对于枚举的 c c c,只要 c c c 不在 S [ j − k + 1 ] , S [ j − k + 2 ] , ⋯ , S [ j + k + 1 ] S[j-k+1],S[j-k+2],\cdots,S[j+k+1] S[j−k+1],S[j−k+2],⋯,S[j+k+1] 内出现过, m a t c h match match 就等于 0 0 0,也就是说它们都相等,此时得到的 x ′ x' x′ 和 s t a ′ sta' sta′ 都是相同的。
所以说,假设 S [ j − k + 1 ] , S [ j − k + 2 ] , ⋯ , S [ j + k + 1 ] S[j-k+1],S[j-k+2],\cdots,S[j+k+1] S[j−k+1],S[j−k+2],⋯,S[j+k+1] 中有 n u m num num 种字符,那么我们一次算出 m a t c h = 0 match=0 match=0 时的 x ′ x' x′ 和 s t a ′ sta' sta′,然后转移 d p [ j + 1 ] [ x ′ ] [ s t a ′ ] ← d p [ j + 1 ] [ x ′ ] [ s t a ′ ] + ( 26 − n u m ) d p [ j ] [ x ] [ s t a ] dp[j+1][x'][sta']\gets dp[j+1][x'][sta']+(26-num)dp[j][x][sta] dp[j+1][x′][sta′]←dp[j+1][x′][sta′]+(26−num)dp[j][x][sta] 即可。
易知 n u m ≤ 2 k + 2 ≤ 9 num\leq 2k+2\leq 9 num≤2k+2≤9,所以这部分我们枚举 c c c 用 t u r n turn turn 数组转移即可。
转移的时间复杂度降低到了约 O ( 10 n k 2 2 k ) O(10nk2^{2k}) O(10nk22k)。
然后能 1s AC 了。(时限 2s)
#include<bits/stdc++.h>
#define K 5
#define N 30010
#define mod 998244353
using namespace std;
inline 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;
}
namespace modular
{
inline int add(int x,int y){if((x+=y)>=mod)x-=mod; return x;}
inline int dec(int x,int y){if((x-=y)<0)x+=mod; return x;}
inline int mul(int x,int y){return 1ll*x*y%mod;}
}
using namespace modular;
struct data
{
int x,sta;
}turn[K][260][520];
int n,k,s[N];
int match[N][26];
int dp[N][K][260];
int f[K<<1],g[K<<1];
char ss[N];
bool vis[30];
int main(){
scanf("%s%d",ss+1,&k);
n=strlen(ss+1);
for(int i=1;i<=n;i++) s[i]=ss[i]-'A';
int pow2k=(1<<(k<<1)),pow2k1=pow2k<<1;
for(int i=1;i<=n;i++)
{
for(int c=0;c<26;c++)
{
int sta=0;
for(int l=i+k;l>=i-k;l--)
{
if(l<1||l>n) sta<<=1;
else sta=sta<<1|(c==s[l]);
}
match[i][c]=sta;
}
}
for(int x=0;x<=k;x++)
{
for(int sta=0;sta<pow2k;sta++)
{
f[k]=x;
for(int i=k+1;i<=2*k;i++) f[i]=f[i-1]+((sta>>(i-1))&1);
for(int i=k-1;i>=0;i--) f[i]=f[i+1]-((sta>>i)&1);
for(int mat=0;mat<pow2k1;mat++)
{
for(int i=1;i<=2*k+1;i++)
{
if((mat>>(i-1))&1) g[i]=f[i-1]+1;
else g[i]=max(g[i-1],f[i]);
}
turn[x][sta][mat].x=g[k+1]-1;
int nsta=0;
for(int i=2*k;i>=1;i--) nsta=nsta<<1|(g[i+1]-g[i]);
turn[x][sta][mat].sta=nsta;
}
}
}
dp[0][k][0]=1;
for(int i=0;i<n;i++)
{
for(int x=0;x<=k;x++)
{
for(int sta=0;sta<pow2k;sta++)
{
if(!dp[i][x][sta]) continue;
int num=0;
for(int j=max(1,i+1-k);j<=min(n,i+1+k);j++)
{
if(!vis[s[j]]) num++;
vis[s[j]]=1;
}
int nx=turn[x][sta][0].x,nsta=turn[x][sta][0].sta;
if(nx>=0) dp[i+1][nx][nsta]=add(dp[i+1][nx][nsta],mul(26-num,dp[i][x][sta]));
for(int j=max(1,i+1-k);j<=min(n,i+1+k);j++)
{
if(!vis[s[j]]) continue;
vis[s[j]]=0;
nx=turn[x][sta][match[i+1][s[j]]].x,nsta=turn[x][sta][match[i+1][s[j]]].sta;
if(nx>=0) dp[i+1][nx][nsta]=add(dp[i+1][nx][nsta],dp[i][x][sta]);
}
}
}
}
int ans=0;
for(int x=0;x<=k;x++)
for(int sta=0;sta<pow2k;sta++)
ans=add(ans,dp[n][x][sta]);
printf("%d\n",ans);
return 0;
}
/*
GUGUA
1
*/