[ZR测试]串
壹、题目描述
为了让沙耶彻底放弃对自己的追求,伊蕾娜在诚实之国的大门上设下了 \(q\) 个密码,但是伊蕾娜太过喜欢自己 自恋 所以有时候会忘记密码,这个时候就连 这位身穿漆黑长袍且头戴三角帽,胸口上别着象征星辰的胸针,灰色的发丝随风摇曳,在太阳照耀下散发出耀眼的光芒,琉璃色的双瞳看似朝向前方,实际上却是眺望着远方的某处。她所经之处树木挥动枝条发出低语,挥洒树叶为她献上祝福。这位具有如此美貌,任谁都只能以惹人怜爱形容的 她也无法通过大门,为此她感到十分苦恼。
但是在某一天,她想到了一个生成密码的方法能让她很好地将大门的密码记录下来,她生成密码规则是这样的:
- 生成 \(n\) 个字符串 \(s_0,s_1,s_2,...s_{n-1}\);
- 对于第 \(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)\).
需要注意这种 "小" 优化,它可能优秀得无法想象。