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;
}
posted @ 2025-04-08 20:13  zxh923  阅读(7)  评论(0)    收藏  举报