押韻(rhyme.cpp/c/pas)
【題目描述】
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.
內存:256M
時間:1s
按照從後到前建TRI樹,那麼一個單詞就能和它的父親,它的兄弟相鄰。
對於一個有單詞結尾的節點i,可以將它的子樹中的最長鏈放在左邊,中間放它的兄弟,右邊放子樹中的次長鏈,這樣就在i節點拼出了一個對於i節點的最優解。
定義f[i]表示以i爲結尾單詞的最長鏈,g[i]表示次長鏈。
f[i] = max(f[son] + sz[son] + isword[son] - 1);
g[i] = min(f[i], f[son] + sz[son] + isword[son] - 1);
答案就是將每個i計算一下最優解,選最優的。
注意這題內存限制很嚴,如果直接開結構體用指針預留26個位置會爆內存,所以選擇set或map來維護並快速查找位置,也可以在外面開數組用整體的鏈式前向星存。
Code:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<map>
using namespace std;
const int sumMax = 3000000;
struct node{
map<char, int> v;
int fa;
bool tg;
}TRI[sumMax + 5];
int N, cnt, Ans;
int sz[sumMax + 5], f[sumMax + 5], g[sumMax + 5];
char S[sumMax + 5];
void Init(){
int len = strlen(S + 1);
int r = 0;
for(int p = len; p; -- p){
if(TRI[r].v.count(S[p]) == 0){
++ cnt;
TRI[r].v.insert(make_pair(S[p], cnt));
TRI[cnt].fa = r;
}
r = TRI[r].v[S[p]];
}
TRI[r].tg = 1;
++ sz[TRI[r].fa];
}
int main(){
freopen("rhyme.in", "r", stdin);
freopen("rhyme.out", "w", stdout);
scanf("%d", &N);
for(int i = 1; i <= N; ++ i){
scanf("%s", S + 1);
Init();
}
for(int i = cnt; i >= 0; -- i) f[i] = g[i] = 1;
for(int i = cnt; i >= 0; -- i) if(TRI[i].tg){
if(f[i]+sz[i]+TRI[i].tg-1 >= f[TRI[i].fa])
g[TRI[i].fa] = f[TRI[i].fa],
f[TRI[i].fa] = f[i]+sz[i]+TRI[i].tg-1;
else if(f[i]+sz[i]+TRI[i].tg-1 > g[TRI[i].fa])
g[TRI[i].fa] = f[i]+sz[i]+TRI[i].tg-1;
}
for(int i = cnt; i >= 0; -- i)
Ans = max(Ans, f[i] + g[i] + sz[i] + TRI[i].tg - 2);
printf("%d\n", Ans);
return 0;
}