題目描述
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; }