题目描述
LCS表示最长公共后缀长度。如果两个单词A,B押韵,当且仅当LCS(A,B)>=MAX(A,B)-1。如果一个序列押韵,当且仅当该序列中任意相邻的两个单词押韵。现在,给你一片文章,文章中没有相同的两个单词。请你从该文章中选择任意单词,并任意排列顺序,得到一个尽量长的押韵序列。注意,每个单词只能出现一次。
输入格式
第一行一个整数N(1<=N<=500000)
接下来N行,每行一个小写字母单词。所有单词的总长度不超过3000000.
输出格式
输出最长的押韵序列的单词个数。
输入样例1
4
honi
toni
oni
ovi
输出样例1
3
输入样例2
5
ask
psk
krafna
sk
k
输出样例2
4
输入样例3
5
pas
kompas
stas
s
Nemarime
输出样例3
1
样例2解释:唯一的押韵序列是ask-psk-sk-k
样例3解释:没有两个单词押韵,所以输出1。
题解:
trie树+DP。
首先构建trie树,对于trie树中的节点,它只能和它的父亲节点、兄弟节点、儿子节点所表示的字符串押韵。
f[i]表示以节点i的儿子为根的子树中最长押韵序列,g[i]为次长押韵序列。
f[i]=f[j]+j.sons,其中j为拥有最长押韵序列的一个儿子。
g[i]=f[k]+k.sons,其中k为拥有次长押韵序列的一个儿子。
最后加上i的其他儿子(即i的儿子的兄弟)。
以i为根的子树中形成的最长押韵序列长度ans=max(f[i]+g[i]+i.sons-2+(i是否带有结束标记?1:0)
#include<cstring> #include<cstdio> #include<algorithm> using namespace std; const int N=3000005; int n, sum, f[N], g[N]; int fir[N], pcnt=1, ecnt; struct node{ int e, next; } edge[N]; void Link( int s, int e ) { edge[++ecnt].e=e; edge[ecnt].next=fir[s]; fir[s]=ecnt; } int fa[N], sons[N], v[N]; bool ed[N]; char s[N]; void Build_tree( char s[] ) { int rot=1, len=strlen(s), w; while( --len>=0 ) { bool flg=1; w=s[len]-'a'+1; for( int i=fir[rot]; i && flg; i=edge[i].next ) if( v[ edge[i].e ]==w ) rot=edge[i].e, flg=0; if( flg ) break; } while( len>=0 ) { v[ ++pcnt ]=s[len--]-'a'+1; Link( rot, pcnt ); fa[pcnt]=rot; rot=pcnt; } ed[rot]=1; sons[ fa[rot] ]++; } void Dp() { for( int i=pcnt; i; i-- ) f[i]=g[i]=1; for( int i=pcnt; i; i-- ) if( ed[i] ) { if( f[ fa[i] ]<=f[i]+sons[i]+ed[i]-1 ) { g[ fa[i] ]=f[ fa[i] ]; f[ fa[i] ]=f[i]+sons[i]+ed[i]-1; } else g[ fa[i] ]=max( g[ fa[i] ], f[i]+sons[i]+ed[i]-1 ); } for( int i=pcnt; i; i-- ) sum=max( sum, f[i]+g[i]+sons[i]+ed[i]-2 ); } int main() { scanf( "%d", &n ); for( int i=1; i<=n; i++ ) { scanf( "%s", s ); Build_tree(s); } Dp(); printf( "%d\n", sum ); return 0; }