news 2026/7/17 6:39:14

UVa 12244 Growing Strings

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
UVa 12244 Growing Strings

题目描述

Gene\texttt{Gene}GeneGina\texttt{Gina}Gina有一个特别的农场,他们种植的是字符串。字符串的生长规则是:只能在左侧和 / 或右侧添加字符,不能丢失字符,也不能在中间插入字符。

他们有若干张照片,每张照片展示一个字符串。由于照片没有标注,他们忘记了每张照片属于哪个字符串。现在需要找出一个最长的照片序列,使得序列中前一张照片的字符串是后一张照片的字符串的连续子串,且所有照片互不相同。

给定若干字符串(照片),求最长合法序列的长度。

输入格式

每个测试用例第一行一个整数NNN1≤N≤1041 \le N \le 10^41N104),接下来NNN行每行一个不同的非空字符串,由小写字母组成,长度不超过100010001000。所有字符串的总长度不超过10610^6106

输入以一行000结束。

输出格式

对于每个测试用例,输出一行一个整数,表示最长序列的长度。

样例

输入

6 plant ant cant decant deca an 2 supercalifragilisticexpialidocious rag 0

输出

4 2

题目分析

本题的核心是:在给定的字符串集合中,找到最长的链s1,s2,…,sks_1, s_2, \dots, s_ks1,s2,,sk,使得sis_isisi+1s_{i+1}si+1连续子串,并且所有字符串互不相同。

直接暴力枚举所有字符串对,判断子串关系,复杂度为O(N2⋅L)O(N^2 \cdot L)O(N2L),不可接受(NNN最大10410^4104)。

考虑生长规则:每次只能在两端添加字符。这意味着,如果sis_isisi+1s_{i+1}si+1的子串,那么si+1s_{i+1}si+1一定可以通过在sis_isi的左右两侧添加若干字符得到。因此,子串关系具有传递性

我们可以将所有字符串按长度从小到大排序。设dp[s]\textit{dp}[s]dp[s]表示以字符串sss结尾的最长链的长度,则有:

dp[s]=max⁡{dp[t]+1∣t 是 s 的子串且 t≠s} \textit{dp}[s] = \max \{ \textit{dp}[t] + 1 \mid t \text{ 是 } s \text{ 的子串且 } t \neq s \}dp[s]=max{dp[t]+1ts的子串且t=s}

初始条件:dp[s]=1\textit{dp}[s] = 1dp[s]=1(单个字符串自己形成长度为111的链)。

问题的关键转化为:对于当前字符串sss,如何快速找到所有属于集合且是sss的子串的ttt

解题思路

子串匹配的优化

枚举sss的所有子串并判断是否在集合中,时间复杂度为O(L2)O(L^2)O(L2)LLL为字符串长度),总复杂度O(∑L2)O(\sum L^2)O(L2),在LLL较大时可能超时。

我们需要一个能够快速匹配多个模式串的数据结构 ——Aho-Corasick\texttt{Aho-Corasick}Aho-Corasick自动机

Aho-Corasick\texttt{Aho-Corasick}Aho-Corasick自动机简介

Aho-Corasick\texttt{Aho-Corasick}Aho-Corasick自动机是一种多模式匹配算法,可以在O(文本长度+模式串总长)O(\text{文本长度} + \text{模式串总长})O(文本长度+模式串总长)的时间内,找出文本串中所有出现的模式串。

其核心结构包括:

  • Trie\texttt{Trie}Trie树:存储所有模式串。
  • fail\textit{fail}fail指针:类似KMP\texttt{KMP}KMPnext\textit{next}next数组,用于在匹配失败时跳转到最长后缀节点。
  • output\textit{output}output标记:标记该节点对应一个模式串的结尾。

算法流程

  1. 读入所有字符串,存入数组。
  2. 按长度排序:保证处理长字符串时,所有比它短的字符串的dp\textit{dp}dp值已经计算完毕。
  3. 将所有字符串插入Aho-Corasick\texttt{Aho-Corasick}Aho-Corasick自动机
  4. 构建fail\textit{fail}fail指针,同时将fail\textit{fail}fail链上的output\textit{output}output信息通过按位或传递到当前节点(即out[u]∣=out[fail[u]]\textit{out}[u] \mid= \textit{out}[\textit{fail}[u]]out[u]∣=out[fail[u]])。这样,每个节点的out[u]\textit{out}[u]out[u]就表示:以该节点为结尾的字符串,或者它的某个后缀是模式串。
  5. 按长度递增顺序处理每个字符串
    • 在自动机上匹配当前字符串,每匹配一个字符,就沿着fail\textit{fail}fail链向上检查所有有out\textit{out}out标记的节点,更新当前字符串的dp\textit{dp}dp值。
    • 匹配结束后,将当前字符串的dp\textit{dp}dp值存储到其对应的终止节点上(即dp[u]=max⁡(dp[u],best)\textit{dp}[u] = \max(\textit{dp}[u], \textit{best})dp[u]=max(dp[u],best))。
  6. 记录全局最大值作为答案。

正确性说明

  • 由于我们按长度从小到大处理,当处理到字符串sss时,所有长度小于∣s∣|s|s的字符串的dp\textit{dp}dp值已经计算完毕。
  • 在自动机上匹配sss的过程中,每到达一个节点uuu,沿着fail\textit{fail}fail链向上即可访问到所有以当前位置结尾的模式串,这些模式串都是sss的子串。
  • 因此,dp[s]\textit{dp}[s]dp[s]能够正确地从其所有子串的dp\textit{dp}dp值转移而来。

复杂度分析

  • 构建Trie\texttt{Trie}Trie树:O(∑∣si∣⋅Σ)O(\sum |s_i| \cdot \Sigma)O(siΣ),其中Σ=26\Sigma = 26Σ=26
  • 构建fail\textit{fail}fail指针:O(∑∣si∣⋅Σ)O(\sum |s_i| \cdot \Sigma)O(siΣ)
  • 匹配过程:每个字符串的每个字符,沿着fail\textit{fail}fail链向上跳的次数在均摊意义下为O(1)O(1)O(1)(因为每个字符的fail\textit{fail}fail跳转次数与树高有关,但通过out\textit{out}out传递优化后,实际总复杂度为O(∑∣si∣)O(\sum |s_i|)O(si))。
  • 总时间复杂度:O(∑∣si∣⋅Σ)O(\sum |s_i| \cdot \Sigma)O(siΣ),空间复杂度:O(∑∣si∣⋅Σ)O(\sum |s_i| \cdot \Sigma)O(siΣ)

代码实现

// Growing Strings// UVa ID: 12244// Verdict: Accepted// Submission Date: 2026-06-03// UVa Run Time: 1.680s//// 版权所有(C)2026,邱秋。metaphysis # yeah dot net#include<bits/stdc++.h>usingnamespacestd;namespaceAhoCorasick{constintMAXN=1e6+6;constintALPHA=26;constcharBASE='a';intnxt[MAXN][ALPHA],fail[MAXN],out[MAXN],dp[MAXN];intnodeCnt;voidinitialize(){for(inti=0;i<nodeCnt;++i){memset(nxt[i],0,sizeof(nxt[i]));fail[i]=out[i]=dp[i]=0;}nodeCnt=1;}voidinsert(conststring&s){intu=0;for(charc:s){intidx=c-BASE;if(!nxt[u][idx])nxt[u][idx]=nodeCnt++;u=nxt[u][idx];}out[u]=1;}voidbuild(){queue<int>q;for(inti=0;i<ALPHA;++i){if(nxt[0][i]){fail[nxt[0][i]]=0;q.push(nxt[0][i]);}}while(!q.empty()){intu=q.front();q.pop();out[u]|=out[fail[u]];for(inti=0;i<ALPHA;++i){intv=nxt[u][i];if(v){fail[v]=nxt[fail[u]][i];q.push(v);}else{nxt[u][i]=nxt[fail[u]][i];}}}}intsolve(conststring&s){intu=0,best=1;for(charc:s){u=nxt[u][c-BASE];for(intv=u;v;v=fail[v]){if(out[v])best=max(best,dp[v]+1);}}dp[u]=max(dp[u],best);returnbest;}}intmain(){ios::sync_with_stdio(false);cin.tie(nullptr);intn;while(cin>>n,n){vector<string>strs(n);for(inti=0;i<n;++i)cin>>strs[i];sort(strs.begin(),strs.end(),[](conststring&a,conststring&b){returna.size()<b.size();});AhoCorasick::initialize();for(conststring&s:strs)AhoCorasick::insert(s);AhoCorasick::build();intans=0;for(conststring&s:strs)ans=max(ans,AhoCorasick::solve(s));cout<<ans<<'\n';}return0;}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/17 6:38:19

人工智能发展历程与核心技术解析

1. 人工智能的本质与演进脉络2006年多伦多大学教授Geoffrey Hinton在《Science》发表的论文&#xff0c;首次提出了深度学习的概念框架。这个时间节点常被视为现代人工智能发展的分水岭&#xff0c;但AI的思想渊源其实可以追溯到更早的时期。1.1 从图灵测试到机器学习1950年艾伦…

作者头像 李华
网站建设 2026/7/17 6:37:34

Linux包管理利器Yum详解:从基础到高级应用

1. Yum命令基础解析yum&#xff08;Yellowdog Updater Modified&#xff09;作为RHEL/CentOS系列发行版的经典包管理工具&#xff0c;其设计哲学体现了Linux系统管理的优雅性。不同于手动处理rpm包的依赖地狱&#xff0c;yum通过仓库元数据索引和依赖解析算法&#xff0c;实现了…

作者头像 李华
网站建设 2026/7/17 6:37:31

Windows应用商店一键修复方案与批处理脚本实现

1. 项目概述&#xff1a;Windows应用商店快速恢复方案遇到Windows 11/10系统Microsoft Store无法打开的情况时&#xff0c;多数教程会建议用户通过运行命令&#xff08;wsreset.exe&#xff09;或PowerShell命令进行修复。但实际使用中&#xff0c;这些方法往往存在两个痛点&am…

作者头像 李华
网站建设 2026/7/17 6:37:22

MP2759数字PWM调流方案在电池充电管理中的应用

1. MP2759充电IC与数字PWM调流方案概述在工业医疗设备、无人机和电动工具等应用中&#xff0c;电池系统的充电管理往往需要动态调节能力。传统方案通过硬件电阻设定固定充电电流&#xff0c;而MP2759的创新之处在于支持通过MCU的PWM信号实时调整输入充电电流&#xff08;ICC&am…

作者头像 李华
网站建设 2026/7/17 6:36:12

Linux日志排查四大神器:tail、cat、less、grep实战指南

1. Linux日志排查工具全景解析在服务器运维和系统故障排查中&#xff0c;日志分析占据了工程师70%以上的工作时间。面对动辄几个GB的日志文件&#xff0c;如何快速定位问题&#xff1f;这就像在茫茫大海中寻找一根特定的针。经过15年运维实战&#xff0c;我总结出一套高效的日志…

作者头像 李华
网站建设 2026/7/17 6:35:02

基于Ollama和LangChain的RAG智能问答系统搭建指南

1. 项目概述&#xff1a;当大语言模型遇上外部知识库去年第一次在本地部署大语言模型时&#xff0c;最大的痛点就是模型对专业领域知识的"信口开河"。直到尝试了RAG&#xff08;检索增强生成&#xff09;架构&#xff0c;才真正让7B参数的"小模型"展现出了…

作者头像 李华