[ZR测试]串

壹、题目描述

为了让沙耶彻底放弃对自己的追求,伊蕾娜在诚实之国的大门上设下了 \(q\) 个密码,但是伊蕾娜太过喜欢自己 自恋 所以有时候会忘记密码,这个时候就连 这位身穿漆黑长袍且头戴三角帽,胸口上别着象征星辰的胸针,灰色的发丝随风摇曳,在太阳照耀下散发出耀眼的光芒,琉璃色的双瞳看似朝向前方,实际上却是眺望着远方的某处。她所经之处树木挥动枝条发出低语,挥洒树叶为她献上祝福。这位具有如此美貌,任谁都只能以惹人怜爱形容的 她也无法通过大门,为此她感到十分苦恼。

但是在某一天,她想到了一个生成密码的方法能让她很好地将大门的密码记录下来,她生成密码规则是这样的:

  1. 生成 \(n\) 个字符串 \(s_0,s_1,s_2,...s_{n-1}\)
  2. 对于第 \(x\) 个密码,有 \(\text{password}_x=\text{LCS}(s_{p_{x,1}},s_{p_{x,2}})\),其中 \(\text{LCS}(x,y)\) 表示字符串 \(x\) 与字符串 \(y\) 的最长公共子串;

在生成完密码之后,伊蕾娜十分放心地离去了。

由于伊蕾娜针对她的密码生成方式设计了一套完整的魔法术式,所以她可以十分快速地得到密码,但是沙耶并不知道,所以她被困在门前已经很久了。

她实在是太喜欢伊蕾娜了 看来我们只能结婚了!,在她进过自己的努力计算出几组密码之后,她已经筋疲力竭,所以她向你求助,让你帮助她也设计出一个魔法术式能够快速地计算出大门的密码。

但是你不会魔法术式,所以你就只能设计一个代码来帮助她了。

数据范围与时空限制

定义 \(N=\sum_{i=0}^n|s_i|\).

那么有 \(N\le 50000,m\le 100000,0\le p_{x,1},p_{x,2}\le n-1\).

时空限制 \(1s,256MB\).

贰、题解

首先,我们可以考虑 \(\mathcal O(Nq)\) 的暴力,对于每个串暴力建出 \(\tt SAM\),然后在上面跑匹配。

考虑一个优化,我们能不能让匹配串长度变小一点,对于长串建出 \(\tt SAM\),让短串跑,顺便加上一个记忆化,具体地说,即对于一个询问 \(p_{x,1},p_{x,2}\),将长串建出 \(\tt SAM\),短串跑。

这是可以的,并且复杂度是 \(\mathcal O(N\sqrt N+q\sqrt N)\) 左右,为什么是这样?我们考虑对于一个建出 \(\tt SAM\) 的串,其他串都在它上面跑匹配,时间复杂度是匹配串长度求和,对于这个匹配串,我们分情况讨论:

  • 当匹配串长度小于 \(\sqrt N\),由于询问有 \(q\) 个,所以这部分复杂度是 \(q\sqrt N\)
  • 当匹配串长度大于 \(\sqrt N\),当它要作为匹配串时,另外一个串长度必定大于它,亦大于 \(\sqrt N\),而这样的串至多有 \(\sqrt N\) 个,记这个串长度为 \(l_i\),那么这部分复杂度就是 \(\left(\sum_il_i\right)\times \sqrt N\le N\sqrt N\)

但是保证这个复杂度的前提是记忆化. 这个复杂度可以接受,所以这个题就做完了。

叁、代码

\(\color{red}{\text{talk is temp, show you the code.}}\)

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;

const int maxn=5e4;
const int maxsize=maxn<<1;
const int maxq=1e5;

int trie[maxsize+5][26];
int fa[maxsize+5], lenth[maxsize+5];
int lst, ncnt;
inline void cls(){
	for(int i=0; i<=ncnt; ++i){
		for(int j=0; j<26; ++j) trie[i][j]=0;
		fa[i]=lenth[i]=0;
	}
	lst=ncnt=1;
}
inline void add(const int c){
	int p=lst;
	int u=lst=++ncnt;
	for(; p && !trie[p][c]; p=fa[p])
		trie[p][c]=u;
	if(!p) fa[u]=1;
	else{
		int q=trie[p][c];
		if(lenth[q]==lenth[p]+1) fa[u]=q;
		else{
			int split=++ncnt;
			for(int i=0; i<26; ++i)
				trie[split][i]=trie[q][i];
			fa[split]=fa[q], lenth[split]=lenth[p]+1;
			fa[u]=fa[q]=split;
			for(; p && trie[p][c]==q; p=fa[p])
				trie[p][c]=split;
		}
	}
}
inline int match(vector<char>s){
	int p=1, ret=0, len=s.size(), now=0;
	for(int i=0; i<len; ++i){
		int to=s[i]-'a';
		while(p && !trie[p][to])
			p=fa[p], now=min(now, lenth[p]);
		if(!p) p=1, now=0;
		else p=trie[p][to], ++now;
		ret=max(ret, now);
	}
	return ret;
}
inline void build(vector<char>s){
	cls(); int len=s.size();
	for(int i=0; i<len; ++i) add(s[i]-'a');
}

vector<char>s[maxn+5];
char ch[maxn+5];
struct node{
	int x, id;
	node(){}
	node(const int X, const int I): x(X), id(I){}
	inline int operator <(const node rhs) const{
		return x<rhs.x;
	}
};
vector<node>q[maxn+5];

int ans[maxq+5], n, Q;

inline void input(){
	scanf("%d %d", &n, &Q);
	for(int i=1; i<=n; ++i){
		scanf("%s", ch+1);
		for(int j=1; ch[j]; ++j)
			s[i].push_back(ch[j]);
	}
}

inline void getquery(){
	int x, y;
	for(int i=1; i<=Q; ++i){
		scanf("%d %d", &x, &y); ++x, ++y;
		if(s[x].size()<s[y].size())swap(x, y);
		q[x].push_back(node(y, i));
	}
}

inline void solve(){
	for(int i=1; i<=n; ++i){
		build(s[i]);
		sort(q[i].begin(), q[i].end());
		int now, ret;
		while(!q[i].empty()){
			now=q[i].back().x;
			ret=match(s[now]);
			while(!q[i].empty() && q[i].back().x==now){
				ans[q[i].back().id]=ret;
				q[i].pop_back();
			}
		}
	}
}

inline void print(){
	for(int i=1; i<=Q; ++i)
		printf("%d\n", ans[i]);
}

signed main(){
	input();
	getquery();
	solve();
	print();
	return 0;
}

用到の小 \(\tt trick\)

三元环计数的时间复杂度分析,再写一遍:

对于这个匹配串,我们分情况讨论:

  • 当匹配串长度小于 \(\sqrt N\),由于询问有 \(q\) 个,所以这部分复杂度是 \(q\sqrt N\)
  • 当匹配串长度大于 \(\sqrt N\),当它要作为匹配串时,另外一个串长度必定大于它,亦大于 \(\sqrt N\),而这样的串至多有 \(\sqrt N\) 个,记这个串长度为 \(l_i\),那么这部分复杂度就是 \(\left(\sum_il_i\right)\times \sqrt N\le N\sqrt N\)

保证复杂度的前提是记忆化,综上,复杂度为 \(\mathcal O(N\sqrt N+q\sqrt N)\).

需要注意这种 "小" 优化,它可能优秀得无法想象。

posted @ 2021-02-19 22:28  Arextre  阅读(54)  评论(0编辑  收藏  举报