洛谷P2292 [HNOI2004] L 语言 题解
题意:
标点符号的出现晚于文字的出现,所以以前的语言都是没有标点的。现在你要处理的就是一段没有标点的文章。
一段文章 $T$ 是由若干小写字母构成。一个单词 $W$ 也是由若干小写字母构成。一个字典 $D$ 是若干个单词的集合。我们称一段文章 $T$ 在某个字典 $D$ 下是可以被理解的,是指如果文章 $T$ 可以被分成若干部分,且每一个部分都是字典 $D$ 中的单词。
例如字典 $D$ 中包括单词 $\texttt{is},\texttt{name},\texttt{what},\texttt{your}$,则文章 $\texttt{whatisyourname}$ 是在字典 $D$ 下可以被理解的,因为它可以分成 $4$ 个单词:$\texttt{what},\texttt{is},\texttt{your},\texttt{name}$,且每个单词都属于字典 $D$,而文章 $\texttt{whatisyouname}$ 在字典 $D$ 下不能被理解,但可以在字典 $D’=D\cup\{\texttt{you}\}$ 下被理解。这段文章的一个前缀 $\texttt{whatis}$,也可以在字典 $D$ 下被理解,而且是在字典 $D$ 下能够被理解的最长的前缀。
给定一个字典 $D$,你的程序需要判断若干段文章在字典 $D$ 下是否能够被理解。并给出其在字典 $D$ 下能够被理解的最长前缀的位置。
- 对于 $100\%$ 的数据,保证 $1 \leq n \leq 20$,$1 \leq m \leq 50$,$1 \leq |s| \leq 20$,$1 \leq |t| \leq 2 \times 10^6$,$s$ 与 $t$ 中均只含小写英文字母。
设 $f_i$ 表示前 $i$ 个字符是否可行,则有
其中 $t[j+1,i]$ 表示文本串的 $[j+1,i]$ ,$S$ 表示所有 $s_i$ 构成的不可重集
注意到 $1\le |s_i| \le 20$ ,因此我们可以建 $\tt{AC}$ 自动机然后暴力转移。
具体地,我们把所有 $s_i$ 都插入 $\tt{AC}$ 自动机,
然后预处理出每个结点在 $\text{fail}$ 树上的 $i (i \le 20)$ 级祖先(认为儿子存在指向父亲的边)
这个信息可以压缩为一个二进制数,不妨设为 $g_u$ 。
转移时,设当前所在的结点为 $u$ ,同时维护 $f_j(i-20 < j \le i)$ 的状态。
后者又可以压到一个二进制数 $x$ 里,即 $x$ 二进制下第 $k(0\le k \le 20)$ 位为 $1$ 表示 $f_{i-k-1}=\text{True}$ 。
转移时,若 $g_u \text{ and } x = \text{True}$ ,则 $f_i=\text{True}$ 。这里的 $\text{and}$ 表示二进制下按位与。
时间复杂度 $O(20 \times \sum s_i + \sum t_i)$
代码:
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iomanip>
#include <random>
#include <queue>
#include <bitset>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N (int)(405)
#define M (int)(2e6+15)
int n,m;
bitset<M> f;
char s[N],t[M];
struct Trie
{
int tot,trie[N][26],ed[N],dep[N],g[N],fail[N];
void insert(char *s)
{
int u=0,l=strlen(s+1);
for(int i=1; i<=l; i++)
{
int c=s[i]-'a';
if(!trie[u][c]) trie[u][c]=++tot;
u=trie[u][c];
}
ed[u]=1; dep[u]=l;
}
void build()
{
queue<int> q;
for(int i=0; i<26; i++)
if(trie[0][i]) q.push(trie[0][i]);
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i=0; i<26; i++)
{
if(trie[u][i])
{
fail[trie[u][i]]=trie[fail[u]][i];
q.push(trie[u][i]);
}else trie[u][i]=trie[fail[u]][i];
}
}
for(int u=1; u<=tot; u++)
for(int i=u; i; i=fail[i])
if(ed[i] && dep[i] <= 21) g[u]|=(1ll << (dep[i]-1));
}
int solve(char *s)
{
int l=strlen(s+1),u=0,x=0;
for(int i=1; i<=l; i++)
{
u=trie[u][s[i]-'a'];
x=((x<<1) | f[i-1]) & ((1ll<<21)-1);
f[i]=(x&g[u])!=0;
}
for(int i=l; i>=1; i--) if(f[i]) return i;
return 0;
}
}tr;
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
cin >> n >> m;
for(int i=1; i<=n; i++)
{
cin >> (s+1);
tr.insert(s);
} tr.build(); f[0]=1;
while(m--)
{
cin >> (t+1);
cout << tr.solve(t) << '\n';
}
return 0;
}