P3715 [BJOI2017] 魔法咒语
P3715 [BJOI2017] 魔法咒语
题意
用 \(n\) 个字符串拼成一个长为 \(L\) 的长串,长串中不能出现另外的 \(m\) 个字符串,求总方案数。
思路
限制条件为忌讳词语不能匹配上拼成的长串。
所以我们把忌讳词语都扔到 AC 自动机上做 dp。
令 \(S_i\) 表示基本词汇,\(trnas_{u,i}\) 表示 fail 树上节点 \(u\) 拼接上 \(S_i\) 后会转移到的节点。
设 \(dp_{u,l}\) 为当前在 fail 树上节点 \(u\),拼成串长为 \(l\) 时的方案数,
如果存在一个忌讳词语以节点 \(v\) 结尾,那么 \(v\) 及其子树都不能参与转移,此时我们设其对应的 \(trans_{u,i}=0\)。
设 AC 自动机上有 \(c\) 个节点,那么转移很好写:
\[dp_{u,l} = \sum_{v=1}^c \sum_{i=1}^n [trans_{v,i}==u]dp_{v,l-|S_i|}
\]
最后答案为 \(ans = \sum_{u=1}^c dp_{u,L}\)。
然后我们发现这个玩意的 \(L\) 高达 \(10^{18}\),但当 \(L\) 较大时 \(|S_i|\le2\)。所以我们考虑点分治,对数据点。
Subtask 1
前 60pts 我们直接如上 dp。
Subtask 2
中间 20pts,\(|S_i|=1\),\(l\) 只会对 \(l+1\) 有贡献。
艾佛森括号不再作判断语句,而是放进转移里:
\[dp_{u,l} = \sum_{v=1}^c \sum_{i=1}^n [trans_{v,i}==u]\times dp_{v,l-1}
\]
中间的艾佛森括号我们提出来求和:
\[f_{v,u} = \sum_{i=1}^n [trans_{v,i}==u]
\]
转移方程变成:
\[dp_{u,l} = \sum_{v=1}^c f_{v,u} \times dp_{v,l-1}
\]
转移方程现在已经很像矩阵乘法了,所以我们构造转移矩阵优化 dp。
\[F_l =
\begin{bmatrix}
dp_{1,l} \\
dp_{2,l} \\
\vdots \\
dp_{c,l}
\end{bmatrix}
\space
T =
\begin{bmatrix}
f_{1,1} & f_{2,1} & \cdots & f_{c,1} \\
f_{1,2} & f_{2,2} & \cdots & f_{c,2} \\
\vdots & \vdots & \vdots & \vdots \\
f_{1,c} & f_{2,c} & \cdots & f_{c,c}
\end{bmatrix}
\]
\[F_L = T^{L-1}F_1
\]
Subtask 3
最后 20pts,\(|S_i|\le2\),\(l\) 只会对 \(l+1\) 和 \(l+2\) 有贡献。
把对 \(|S_i|\) 的分类讨论放进转移里:
\[dp_{u,l} = \sum_{v=1}^c \sum_{i=1}^n [|S_i|==1][trans_{v,i}==u]dp_{v,l-1}+[|S_i|==2][trans_{v,i}==u]dp_{v,l-2}
\]
分别把两对艾佛森括号提出来:
\[f_{1,v,u} = \sum_{i=1}^n [|S_i|==1][trans_{v,i}==u] \\
f_{2,v,u} = \sum_{i=1}^n [|S_i|==2][trans_{v,i}==u]
\]
转移方程变成了:
\[dp_{u,l} = \sum_{u=1}^c f_{1,v,u}\times dp_{v,l-1}+f_{2,v,u}\times dp_{v,l-2}
\]
现在在矩阵里记录 \(l-1\) 和 \(l-2\) 两个状态就可以了。
\[F_l =
\begin{bmatrix}
dp_{1,l-1} \\
dp_{2,l-1} \\
\vdots \\
dp_{c,l-1} \\
\\
dp_{1,l-2} \\
dp_{2,l-2} \\
\vdots \\
dp_{c,l-2}
\end{bmatrix}
\space
T=
\begin{bmatrix}
f_{1,1,1} & f_{1,2,1} & \cdots & f_{1,c,1} & \space f_{2,1,1} & f_{2,2,1} & \cdots & f_{2,c,1} \\
f_{1,1,2} & f_{1,2,2} & \cdots & f_{1,c,2} & \space f_{2,1,2} & f_{2,2,2} & \cdots & f_{2,c,2} \\
\vdots & \vdots & \vdots & \vdots & \space \vdots & \vdots & \vdots & \vdots \\
f_{1,1,c} & f_{1,2,c} & \cdots & f_{1,c,c} & \space f_{2,1,c} & f_{2,2,c} & \cdots & f_{2,c,c} \\
\\
1 & 0 & \cdots & 0 & \space 0 & 0 & \cdots & 0 \\
0 & 1 & \cdots & 0 & \space 0 & 0 & \cdots & 0 \\
\vdots & \vdots & \vdots & \vdots & \space \vdots & \vdots & \vdots & \vdots \\
0 & 0 & \cdots & 1 & \space 0 & 0 & \cdots & 0
\end{bmatrix}
\]
\[F_L = T^{L-1}F_1
\]
代码
const int N = 55,M = 105;
const ll mod = 1e9+7;
char t[M],S[N][M];
int n,m,L,f2 = 1; ll ans;
int rt = 1,cnt = 1;
struct{
int son[26],fail,trans[N];
int is_ed; // is_ed 是否有一个忌讳词语以该节点结尾
vector <int> g; // fail 树边
}tr[M];
inline void insert(){
int now = rt;
int len = strlen(t+1);
for(int i=1;i<=len;++i){
int c = t[i]-'a';
if(!tr[now].son[c]) tr[now].son[c] = ++cnt;
now = tr[now].son[c];
}
tr[now].is_ed = 1;
}
inline int get_trans(int now,int id){
int len = strlen(S[id]+1);
for(int i=1;i<=len;++i){
int c = S[id][i]-'a';
now = tr[now].son[c];
if(tr[now].is_ed) return 0; // 不能参与转移设成 0
}
return now;
}
inline void get_fail(){
queue <int> q;
for(int i=0;i<26;++i) tr[0].son[i] = 1;
q.push(1);
while(!q.empty()){
int now = q.front();
q.pop();
for(int i=0;i<26;++i){
if(tr[now].son[i]){
tr[tr[now].son[i]].fail = tr[tr[now].fail].son[i];
q.push(tr[now].son[i]);
// 一个节点不能转移,其子树内节点都不能参与转移
tr[tr[now].son[i]].is_ed |= tr[tr[tr[now].son[i]].fail].is_ed;
tr[tr[tr[now].son[i]].fail].g.push_back(tr[now].son[i]);
}
else tr[now].son[i] = tr[tr[now].fail].son[i];
}
}
q.push(1);
while(!q.empty()){
int now = q.front();
q.pop();
if(tr[now].is_ed) continue;
for(int i=1;i<=n;++i) tr[now].trans[i] = get_trans(now,i);
for(int i=tr[now].g.size()-1;i>=0;--i) q.push(tr[now].g[i]);
}
}
inline void solve1(){ // L<=100
ll dp[M][M];
memset(dp,0,sizeof(dp));
dp[1][0] = 1;
for(int len=0;len<L;++len){
for(int now=1;now<=cnt;++now){
if(!dp[now][len]) continue;
for(int i=1;i<=n;++i){
if(tr[now].trans[i] && len+strlen(S[i]+1)<=L){
(dp[tr[now].trans[i]][len+strlen(S[i]+1)] += dp[now][len])%=mod;
}
}
}
}
for(int now=1;now<=cnt;++now) (ans += dp[now][L])%=mod;
}
struct Matrix{
int row,clm;
ll val[M<<1][M<<1];
Matrix(){
row = clm = 0;
memset(val,0,sizeof(val));
}
Matrix(int f){
row = clm = 0;
memset(val,0,sizeof(val));
if(f) for(int i=(M<<1)-1;i;--i) val[i][i] = 1;
}
inline Matrix operator * (const Matrix&G) const{
Matrix res = Matrix(0);
res.row = row; res.clm = G.clm;
for(int k=1;k<=clm;++k){
for(int i=1;i<=row;++i){
for(int j=1;j<=G.clm;++j){
(res.val[i][j] += val[i][k]*G.val[k][j])%=mod;
}
}
}
return res;
}
inline void operator *= (const Matrix&G){
(*this) = (*this)*G;
}
friend inline Matrix quick_pow(Matrix x,int y){
Matrix res = Matrix(1);
res.row = x.row; res.clm = x.clm;
while(y){
if(y&1) res *= x;
x *= x;
y >>= 1;
}
return res;
}
}T,F;
inline void solve2_1(){ // |S|==1
F.row = cnt; F.clm = 1;
for(int i=1;i<=n;++i) F.val[tr[1].trans[i]][1]++;
T.row = cnt; T.clm = cnt;
for(int now=1;now<=cnt;++now){
for(int i=1;i<=n;++i){
T.val[tr[now].trans[i]][now]++;
}
}
F = quick_pow(T,L-1)*F;
for(int now=1;now<=cnt;++now) (ans += F.val[now][1])%=mod;
}
inline void solve2_2(){ // |S|<=2
F.row = cnt*2; F.clm = 1;
for(int i=1;i<=n;++i) F.val[tr[1].trans[i]][1] += (strlen(S[i]+1)==1);
F.val[cnt+1][1] = 1; // dp[1][0] = 1
T.row = cnt*2; T.clm = cnt*2;
for(int now=1;now<=cnt;++now){
for(int i=1;i<=n;++i){
if(strlen(S[i]+1)==1) T.val[tr[now].trans[i]][now]++;
else if(strlen(S[i]+1)==2) T.val[tr[now].trans[i]][now+cnt]++;
T.val[now+cnt][now] = 1;
}
}
F = quick_pow(T,L-1)*F;
for(int now=1;now<=cnt;++now) (ans += F.val[now][1])%=mod;
}
int main(){
read(n,m,L);
for(int i=1;i<=n;++i){
scanf("%s",S[i]+1);
if(strlen(S[i]+1)>1) f2 = 0; // Subtask 2 or 3
}
for(int i=1;i<=m;++i){
scanf("%s",t+1);
insert();
}
get_fail();
L<=100 ? solve1() : f2 ? solve2_1() : solve2_2();
printf("%lld",ans);
return 0;
}

浙公网安备 33010602011771号