【题解】 [HNOI2004] L 语言


题目传送门

题意

题目描述

标点符号的出现晚于文字的出现,所以以前的语言都是没有标点的。现在你要处理的就是一段没有标点的文章。

一段文章 \(T\) 由若干小写字母构成。一个单词 \(W\) 也是由若干小写字母构成。一个字典 \(D\) 是若干个单词的集合。我们称一段文章 \(T\) 在某个字典 \(D\) 下是可以被理解的,是指如果文章 \(T\) 可以被不重不漏地分成若干部分,且每一个部分都是字典 \(D\) 中的单词。

给定一个字典\(D\),你的程序需要判断若干段文章在字典 \(D\) 下是否能够被理解,并给出其在字典 \(D\) 下能够被理解的最长前缀的位置。

输入格式

第一行两个整数 n 和 m,表示字典 D 中有 n 个单词,且有 m 段文章需要被处理。

接下来 n 行,每行一个字符串 s,表示字典 D 中的一个单词。

接下来 m 行,每行一个字符串 t,表示一篇文章。

输出格式

对于输入的每一篇文章,你需要输出一行一个整数,表示这段文章在字典 \(D\) 可以被理解的最长前缀的位置。

思路

由于你谷的数据有加强,所以主要以你谷上的得分为对照。

文本串的前缀\(s[0,i]\)能被理解,当且仅当某个位置\(j \in [0,j)\)满足\(s[0,j]\)能够被理解,并且\(s[j+1,i]\)能够被理解。

一个朴素的想法:
und数组记录文本串\(s[0,und[tmp]]\)恰好能被理解时的位置。文本串从头到尾遍历,每次都枚举und里表示的能被理解的前缀,然后在字典里面验证\(s[und[j]+1,i]\)是否能被理解。最后更新und数组。
f数组记录截至当前的i,能够被理解的最长前缀。由于能被理解的最长前缀长度序列不下降,因此递推时只需要取当前和前一位置f数组的较大值。

朴素的算法必无法从这题手里骗到满分。事实上,\(O(大约n^2)\)的时间复杂度实在算不得优秀。

55pts on Luogu-Code1
#include
using namespace std;
#define ll long long
const int N=2e6+5;
int n,m,lm;
string w;
int trie[500][27],cnt;
bool ex[500];
ll f[N],und[N],tmp;

inline ll lkup(ll l,ll r){
	if(l>r) return 0;
	int p=0;
	ll ret=0;
	for(int i=l;i<=r;i++){
		int c=w[i]-'a';
		if(!trie[p][c]) return ret;
		p=trie[p][c];
		if(ex[p]) ret=i-l+1;
	}
	if(ex[p]) ret=r-l+1;
	return ret;
}

inline ll find(string s){
	memset(f,0,sizeof(f));
	tmp=0; 
	int l=s.length();
	for(int i=0;i=0;j--){
			f[i]=max(f[i],und[j]+1+lkup(und[j]+1,(ll)i));//[j+1,i]
			if(f[i]==i+1) break;
		}
		f[i]=max(f[i],f[i-1]);
		if(f[i]==i+1) und[tmp++]=i;
	}
	return f[l-1];
}

inline void ins(string str){
	int l=str.length();
	lm=max(lm,l);
	int p=0;
	for(int i=0;i'z') ch=getchar();
	while(ch>='a' && ch<='z'){
		s+=ch;
		ch=getchar();
	}
	return s;
}

int main(){
	scanf("%d%d",&n,&m);
	while(n--){
		w=read();
		ins(w);
	}
	while(m--){
		w=read();
		printf("%lld\n",find(w));
	}
	return 0;
}

Many days later……
据说正解是AC自动机,那就先打一个AC自动机板子吧(错误思想)。
怎么保证连着匹配前缀呢?打到最后灵光一闪,直接在trie上比较就可以了啊!哪里用得着什么AC自动机!

这次打出来和Code1有几点不同:

90 pts on Luogu-Code2
#include
using namespace std;
#define ll long long
const int S=2e6+5;
const int N=400;
int n,m,ml;
int tr[N*100][30],cnt;//,fail[N*100](留着做纪念)
char s[N],t[S];
bool ex[N*100],f[S];

inline int query(char r[],int len,int st){
	int p=0,ret=0;
	len=min(st+ml,len);
	for(int i=st;i

90 pts on Luogu-Code
#include
using namespace std;
#define ll long long
const int S=2e6+5;
const int N=400;
int n,m,ml;
int tr[N*100][30],cnt;
char s[N],t[S];
bool ex[N*100],f[S];
ll zt;

inline int query(char r[],int len,int st) {
	int p=0,ret=0;
	len=min(st+ml,len);
	for(int i=st; i>=1,i++;
	while(i>=1; i++;
		while(!(zt&1) && zt) zt>>=1,i++;
	}
	return ret;
}

inline void ins(char r[]) {
	int l=strlen(r);
	ml=max(ml,l);
	int p=0;
	for(int i=0; i

“人类的使命,在于自强不息地追求完美”,文学巨匠托尔斯泰曾言。虽然至此,已经可以ACLOJ上的题目,但是没有过你谷上的加强样例,笔者怎么会止歇?

One day later……

笔者再次遇到了瓶颈。

“不破不立”,只有敢于挑战思维定式,才能创造新的成果,突破瓶颈。我在瞎写

代码实现

AC Code

#include
using namespace std;
#define ll long long
const int S=2e6+5;
const int N=405;
int n,m;
int tr[N*100][30],cnt,ex[N*100],fail[N*100];
int f[S],len[N*100],trans[N*100];
char s[N],t[S];

inline int query(char r[]) {
	int p=0,x=0;
	int l=strlen(r+1);
	f[0]=1;
	for(int i=1; i<=l; i++) {
		p=tr[p][r[i]-'a'];
		x=((x<<1)|f[i-1])&((1<<20)-1);
        f[i]=(x&len[p])!=0;
	}
	for(int i=l;i>=1;i--){
		if(f[i]) return i;
	}
	return 0;
}

inline void build_AC(){
	queue  q;
	for(int i=0;i<26;i++){
		if(tr[0][i]) q.push(tr[0][i]);
	}
	while(!q.empty()){
		int p=q.front();
		q.pop();
		for(int i=0;i<26;i++){
			if(tr[p][i]){
				q.push(tr[p][i]);
				fail[tr[p][i]]=tr[fail[p]][i];
			}
			else tr[p][i]=tr[fail[p]][i];
		}
	}
	for(int i=1;i<=cnt;i++){
		int j=i;
		while(j){
			if(ex[j]) len[i]|=(1<<(ex[j]-1));
			j=fail[j];
		}
	}
}

inline void ins(char r[]) {
	int l=strlen(r);
	int p=0;
	for(int i=0; i