[NOIP模擬賽]押韻

題目描述
LCS表示最長公共後綴長度。如果兩個單詞AB押韻,當且僅當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;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章