[HEOI2015]最短不公共子串

题意:

洛谷链接

给出两个字符串 AB,分别求出:
1.最短的串,是 A 的子串,不是 B 的子串。
2.最短的串,是 A 的子串,不是 B 的子序列。
3.最短的串,是 A 的子序列,不是 B 的子串。
4.最短的串,是 A 的子序列,不是 B 的子序列。


一道四合一题,如果分开做前两个好说,后两个得有点操作 ,不过由于我太菜做不得。而且暂时还不想学后两个点单独怎么做。

那我们把这四个看成一类问题。

考虑自动机:一个节点表示一个状态,每条边表示状态的转移。由于子串和子序列都能建出自动机来,那么我们只需要对两个自动机进行相同的转移,则 A 中有而 B 中没有的状态就是我们要找的答案。

子序列建成自动机这个应该都会,由于希望最长的匹配,肯定相同的字符越靠前越好。每个位置是一个点,向后每个字符往离它最近的点连边,时间复杂度与空间复杂度均为 O(n 字符集大小)

子串建成自动机可以选择前缀自动机也可选择后缀自动机,而这题数据范围是 2000,因为复杂度瓶颈并不在这里,当然如果不想考虑空间的话可以写 sam。我们这里只需要用 trie 当自动机就可以了,把每个子串取出来建立一个 trie,字符总长度 O(n2),时间复杂度与空间复杂度均为 O(n2)。看着大算算其实可过。

那么自动机就建好了,每次询问是什么,就把两个字符串建成相应的自动机就好了。那么现在考虑如何找答案。

因为一条边相当于加进去一个字符,我们进行 BFS,遇到第一个 A 中存在而 B 中不存在的状态就把长度 +1 输出出来。如果遍历 A 的完整个自动机(自动机肯定不会有环的),还没有找到一个 B 中没有的状态,那 A 就太逊了,输出 1

考虑 BFS 复杂度:trie 进行 BFS 时,由于 trie 是像树一样的结构,每个点只会被遍历一次,复杂度不会超过 O(n2) 。而当两个序列自动机进行遍历的时候,由于到达一个点可以通过不同的路径,如果无脑加点会被卡成指数级。我们需要用 vis 数组记录一下哪些状态到达过,由于序列自动机节点有 n 个,这样时间复杂度与空间复杂度也都是 O(n2) 的,而且这个复杂度瓶颈不可避,前面自然也就不需要那么优了。

不过要注意只有两个自动机同时为序列自动机的时候才用 vis,否则 trie 那么多点也开不下是吧~

#include <bits/stdc++.h> using namespace std; const int N=101010; const int qwq=2000100; const int inf=0x3f3f3f3f; struct T { char s[N]; int n; int ch[qwq][26],cnt,now,rt; void trie() { memset(ch,0,sizeof(ch)); rt = now = cnt = 1; for(int i=1;i<=n;i++) { now = rt; for(int j=i;j<=n;j++) { int c = s[j] - 'a'; if(!ch[now][c]) ch[now][c] = ++cnt; now = ch[now][c]; } } } void lie() { memset(ch,0,sizeof(ch)); cnt = n; rt = 0; for(int i=1;i<=n;i++) { for(int j=i-1;j>=0;j--) { ch[j][ s[i]-'a' ] = i; if(s[j]==s[i]) break; } } } } A,B; struct E{ int x,y,le; }; queue <E> q; bool vis[2333][2333]; int query() { bool flag = 0; if(A.rt==0 && B.rt==0) flag = 1; q.push( (E){A.rt,B.rt,0} ); vis[A.rt][B.rt] = 1; while(!q.empty()) { E now = q.front(); q.pop(); for(int i=0;i<25;i++) { int u = A.ch[now.x][i], v = B.ch[now.y][i]; if(flag) { if(vis[u][v]) continue; } if(!u) continue; if(u && !v) { while(!q.empty()) q.pop(); return now.le+1; } q.push( (E){u,v,now.le+1} ); if(flag) vis[u][v] = 1; } } return -1; } int main() { int ans4; scanf("%s%s",A.s+1,B.s+1); A.n = strlen(A.s+1); B.n = strlen(B.s+1); A.trie(); B.trie(); cout<<query()<<"\n"; B.lie(); cout<<query()<<"\n"; A.lie(); ans4 = query(); B.trie(); cout<<query()<<"\n"<<ans4; return 0; }

sam 的做法:

后缀自动机时间建立的复杂度是 O(n) 的,空间复杂度是 O(n26) 的,这让人很舒服,但是由于到达一个点同样有多条路径,我们每次 BFS 都要记录 vis

要注意 sam 数组要开两倍。

#include <bits/stdc++.h> using namespace std; const int N=101010; const int qwq=2000100; const int inf=0x3f3f3f3f; struct T { char s[2333]; int n; int ch[4333][26],fa[4333],len[4333],cnt,now,rt; void insert(int c) { int u = now; now = ++cnt; len[cnt] = len[u] + 1; for(; u&&!ch[u][c]; u=fa[u]) ch[u][c] = cnt; if(!u) fa[cnt] = 1; else { int zjt = ch[u][c]; if(len[zjt]==len[u]+1) fa[cnt] = zjt; else { int ob = cnt+1; memcpy(ch[ob],ch[zjt],sizeof(ch[zjt])); fa[ob] = fa[zjt]; len[ob] = len[u] + 1; fa[cnt] = fa[zjt] = ob; for(; u&&ch[u][c]==zjt; u=fa[u]) ch[u][c] = ob; cnt++; } } } void sam() { memset(ch,0,sizeof(ch)); now = cnt = rt = 1; for(int i=1;i<=n;i++) insert(s[i]-'a'); } void lie() { memset(ch,0,sizeof(ch)); cnt = n; rt = 0; for(int i=1;i<=n;i++) { for(int j=i-1;j>=0;j--) { ch[j][ s[i]-'a' ] = i; if(s[j]==s[i]) break; } } } } A,B; struct E{ int x,y,le; }; queue <E> q; bool vis[4333][4333]; int query() { memset(vis,0,sizeof(vis)); q.push( (E){A.rt,B.rt,0} ); vis[A.rt][B.rt] = 1; while(!q.empty()) { E now = q.front(); q.pop(); for(int i=0;i<25;i++) { int u = A.ch[now.x][i], v = B.ch[now.y][i]; if(vis[u][v] || !u) continue; if(u && !v) { while(!q.empty()) q.pop(); return now.le+1; } q.push( (E){u,v,now.le+1} ); vis[u][v] = 1; } } return -1; } int main() { int ans4; scanf("%s%s",A.s+1,B.s+1); A.n = strlen(A.s+1); B.n = strlen(B.s+1); A.sam(); B.sam(); cout<<query()<<"\n"; B.lie(); cout<<query()<<"\n"; A.lie(); ans4 = query(); B.sam(); cout<<query()<<"\n"<<ans4; return 0; }

__EOF__

本文作者枫叶晴
本文链接https://www.cnblogs.com/maple276/p/12884333.html
关于博主:菜菜菜
版权声明:呃呃呃
声援博主:呐呐呐
posted @   maple276  阅读(224)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示