1 条题解

  • 0
    @ 2025-8-24 22:00:32

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar 三点水一个各
    卷不动了

    搬运于2025-08-24 22:00:32,当前版本为作者最后更新于2020-10-29 11:27:47,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    本题出现公共后缀,考虑将所有单词放在一棵树里做。

    什么是Trie

    Trie 亦称字典树、前缀树,是一种以空间换时间的做法。

    对于一个长度为 LL 的字符串,插入和查询的复杂度均为 O(L)O(L)

    如图所示,将 ab,ad,ace,acf\texttt{ab},\texttt{ad},\texttt{ace},\texttt{acf} 放入 Trie。

    详细的 Trie 插入和查询操作请完成模板题P2580


    建树

    观察本题,需要找到公共后缀,所以将单词反向存入 Trie,如图所示,将ba,da,eca,fca\texttt{ba},\texttt{da},\texttt{eca},\texttt{fca} 放入 Trie。

    题面写到,序列中相邻两个单词需满足 LCS(A,B)max(A,B)1LCS (A,B) ≥ \max(|A|,|B|)-1,所以相邻两个单词要么是父子关系,要么是兄弟关系,如:

    abcd\texttt{abcd}bcd\texttt{bcd} 是父子关系(当然顺序可以调换,bcd\texttt{bcd}abcd\texttt{abcd} 也是合法的);

    abcd\texttt{abcd}bbcd\texttt{bbcd} 是兄弟关系(同理可以调换顺序,bbcd\texttt{bbcd}abcd\texttt{abcd} 也是合法的)。

    所以,我们可以确定在图中的访问顺序是以下三种:访问父结点,访问子结点,访问同父的兄弟结点,如下图所示:

    随后我们发现,在建树的过程中,不是所有结点都能按照如上三种顺序访问,如对 a,ba,dcba\texttt{a},\texttt{ba},\texttt{dcba} 建树时,结点 dd 不能到结点 cc,因为并不存在 cba\texttt{cba} 这个单词,如图所示:

    所以对于每个结点,我们用一个 bb 数组来存储他们出现的次数,当然,由于所有单词互不相同,这个数值只可能是 0011

    a,ba,dcba\texttt{a},\texttt{ba},\texttt{dcba} 建树时,如图所示:

    至此,建树完毕。


    查找

    我们知道,在查找过程中,因为出现次数为 00 的点不可以被经过,所以不会出现 脱 节 的现象。

    并且由于所有单词互不相同,所以不走回头路,即同一结点不经过 22 次。

    所以最后所有序列构成的图是一棵树而不是森林。

    如图是对于 ba,da,eca,fca\texttt{ba},\texttt{da},\texttt{eca},\texttt{fca} 一种可能的情况:

    因此在一种可能的情况中,一个结点可能作为根结点,也可能是非根结点。

    下面我先将情况理想化:

    1. 所有结点 ii 所代表的单词次数均为 11,即 bi=1b_i=1
    2. 该结点是非根结点
    3. 不考虑该结点以上的结点(包括其父结点)

    开一个数组,记 ii 结点作为非根结点时能取得最大单词数为 fif_i

    首先,因为兄弟结点互相之间可以连接,并且访问顺序可以随意,在这里将所有的子节点 jj 都取来(取的是结点 jj 本身,而不是 fjf_j)。

    然后,思考对于 ii 的子结点 jj,在访问到 jj 之前,肯定是由 jj 以下的结点过来的,也就是由 fjf_j 个结点过来的,要使 fif_i 最大,就得找到最大的 fjf_j

    (这里 i,j,fi,fji,j,f_i,f_j 的关系有点复杂,总之就是 jjii 的子结点,fi/jf_{i/j}i/ji/j 作为非根结点时能取得最大单词数,请读者自行梳理)。

    最后可以取到 ii 结点本身,不要漏掉。

    iikk 个子结点,那么

    fi=max(fj1)+k+1f_i=\max(f_j-1)+k+1

    至于为什么是 fj1f_j-1 而不是 fjf_j,是因为 jjfjf_jkk 中均出现一次。

    举个例子:

    对于单词 $\texttt{ba},\texttt{da},\texttt{eca},\texttt{fca},\texttt{ica},\texttt{heca},\texttt{geca},\texttt{jfca}$,以 c\texttt{c} 为非根结点时,如图所示:

    解释一下,此图中,cc 子节点个数 k=3k=3,子节点(e\texttt{e},f\texttt{f},i\texttt{i})中最大的 ff33,故 fc=max(fj1)+k+1=(31)+3+1=6f_c=\max(f_j-1)+k+1=(3-1)+3+1=6

    为什么 fcf_c 只能取子结点 jj 中最大的一个 ff 而不是取多个?

    因为如果取第二个,意味着从一个叶子结点(如 h\texttt{h},g\texttt{g} )经过 c\texttt{c} 到另一个叶子结点(如j\texttt{j}),此时无法再从叶子结点往上,因为所有单词互不相同,那么此时,c\texttt{c} 就成了根节点,与我的假设不符。如图所示:


    接下来考虑当该结点为根结点:

    上面分析非根结点说过,如果取第二个,意味着该结点就是根结点。

    所以以该点为根节点时,最大单词个数是在以该单词为非根结点的基础上,再加上第二大的 fjf_j,即:

    fi=max1(fj1)+max2(fj1)+k+1f'_{i}=max1(f_j-1)+max2(f_j-1)+k+1

    接下来考虑该结点对应单词个数为 00,即 bi=0b_i=0:

    对于一个对应单词个数为 00 的结点,不能将它忽略。

    因为如果他存在对应单词个数不为 00 的子结点,即 bj=1b_j=1

    这些 bj=1b_j=1 的子结点之间扔可以相互访问,

    这是只需要把公式中最后加上去的 11 删掉就可以了。

    复杂度

    设单词个数为 NN,长度之和为 SS

    建树复杂度 O(s)O(s)

    查找复杂度 O(26N)O(26N)

    Code\mathtt{Code}

    #include<bits/stdc++.h>
    using namespace std;
    int a[2000003][27],b[2000003],f[2000003];//3e6似乎要炸
    int n,m,l1,l2,num=0,ans=0;
    string s;
    int main()
    {
    	scanf("%d",&n);
    	memset(a,0,sizeof(a));
    	for(int k=1;k<=n;++k) //Trie 建树
    	{
    		int x=0;
    		cin>>s;
    		for(int i=s.length()-1;i>=0;--i)
    		{
    		  if(!a[x][s[i]-'a']) 
    		    a[x][s[i]-'a']=++num;
    		  x=a[x][s[i]-'a']; 
    	    }
    	    b[x]++; //单词个数
    	}
    	for(int i=num;i>=0;--i)
    	{
    		f[i]=b[i]; //该节点单词个数,是1或是0
    		l1=l2=0; //最大和次大
    		for(int j=0;j<=25;++j) 
    		  if(b[a[i][j]]) //存在该子结点,若该结点对应单词数为0,没有意义。
    		  {
    		  	  f[i]++;  //选取子结点本身
    		  	  if(f[a[i][j]]-1>l1) l2=l1,l1=f[a[i][j]]-1; //更新最长
    		  	  else if(f[a[i][j]]-1>l2) l2=f[a[i][j]]-1;//更新次长
    		  }
    		f[i]+=l1; //非根结点最多单词
    		ans=max(ans,f[i]+l2); //根节点最多单词。
    	}
    	printf("%d",ans);
    	return 0;
     } 
    
    • 1

    信息

    ID
    3438
    时间
    1000ms
    内存
    250MiB
    难度
    5
    标签
    递交数
    0
    已通过
    0
    上传者