Luogu P2292 L语言
我们称前 \(n\) 个串为字典。
80(90)pts
考虑建出所有字典串的字典树,然后对于每个询问串,我们进行 dp。
设 \(f_i\) 表示长度为 \(i\) 的前缀是否能够完全消除。显然有 \(f_0=1\)。
设询问串为 \(t\),若 \(f_i=1\),则我们从 \(t_i\) 开始向后跳字典树,如果跳了 \(x\) 步后一个点有结束标记,那么 \(f_{i+x}\leftarrow 1\)。
这个东西是 \(O(n\sum |t|)\) 的,理应获得 \(80\) 分,实际有 \(90\) 分。
正解
发现瓶颈在于我们要找每个位置 \(i\) 能转移到哪些位置 \(j\),我们换个思路,考虑每个位置 \(i\) 能被哪些位置 \(j\) 转移到。
发现一个位置 \(i\) 能被位置 \(j\) 转移到,当且仅当 \(f_j=1\) 且 \(t_{j+1}\sim t_i\) 在字典里。
由于字典串的长度都很小,所以事实上转移的位置不超过 \(20\) 个。
我们可以存一个二进制数 \(cur\) 表示 \(i\) 前面的 \(20\) 个位置哪些位置的 \(f\) 是 \(1\)。
然后现在的问题变成了,对于一个位置 \(i\),找到 \(i\) 哪些长度的后缀是字典串,记为一个二进制数 \(g_i\)。显然可以一直在 AC 自动机上跳,假设到位置 \(i\) 时在 AC 自动机上跳到了点 \(p\),那么求 \(g_i\) 相当于求 \(g_p\)。
这个东西我们可以直接在 AC 自动机上拓扑排序,具体地,我们建出 \(fail\) 指针的反边,然后对于每个 AC 自动机上的点,其 \(g\) 值或上其父亲当前的值。
然后我们就会求 \(g_i\) 了。
现在只需要看一下 \(g_i\) 和 \(cur\) 有没有同时为 \(1\) 的位即可。
AC code:
#include<bits/stdc++.h>
#define int long long
#define N 2000005
#define pii pair<int,int>
#define x first
#define y second
using namespace std;
int T=1,n,m,idx=1,din[N],state[N];
string s;
struct acam{
struct node{
int s[26],ne,ed;
}tr[405];
vector<int>e[405];
void add(int a,int b){
e[a].push_back(b);
}
void ins(string s){
int p=1,siz=s.size();
for(int i=0;i<siz;i++){
int t=s[i]-'a';
if(!tr[p].s[t])tr[p].s[t]=++idx;
p=tr[p].s[t];
}
tr[p].ed=1<<siz;
}
void build(){
queue<int>q;
for(int i=0;i<26;i++){
tr[0].s[i]=1;
}
q.push(1);
while(!q.empty()){
int u=q.front();
q.pop();
int ne=tr[u].ne;
for(int i=0;i<26;i++){
int v=tr[u].s[i];
if(!v){
tr[u].s[i]=tr[ne].s[i];
continue;
}
tr[v].ne=tr[ne].s[i];
din[v]++;
add(tr[v].ne,v);
q.push(v);
}
}
}
void topo(){
queue<int>q;
for(int i=1;i<=idx;i++){
if(!din[i]){
q.push(i);
}
}
while(!q.empty()){
int u=q.front();
q.pop();
state[u]=tr[u].ed;
for(auto v:e[u]){
tr[v].ed|=state[u];
if(!--din[v])q.push(v);
}
}
}
}ac;
void solve(int cs){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>s;
ac.ins(s);
}
ac.build();
ac.topo();
int mod=1<<21;
while(m--){
string t;
cin>>t;
int siz=t.size();
t=' '+t;
int cur=2;
int p=1;
int res=0;
for(int i=1;i<=siz;i++){
p=ac.tr[p].s[t[i]-'a'];
// cout<<i<<' '<<p<<' '<<' '<<state[p]<<' '<<cur<<' '<<(state[p]&cur)<<'\n';
if((state[p]&cur)!=0)res=i;
cur<<=1;
cur%=mod;
if(res==i)cur|=2;
}
cout<<res<<'\n';
}
}
void solution(){
/*
记录每个位置能向前匹配的长度(二进制数)
记录每个位置前20个位置是否可以到达(二进制数)
两个东西与一下就可以得到这一位的f
第一个东西需要使用ac自动机
*/
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// cin>>T;
for(int cs=1;cs<=T;cs++){
solve(cs);
}
return 0;
}