1. 程式人生 > >upc 6360: 詞韻(字典樹上dp)

upc 6360: 詞韻(字典樹上dp)

6360: 詞韻

時間限制: 2 Sec  記憶體限制: 128 MB
提交: 134  解決: 18
[提交] [狀態] [討論版] [命題人:admin]

題目描述

Adrian 很喜歡詩歌中的韻。他認為,兩個單詞押韻當且僅當它們的最長公共 字尾的長度至少是其中較長單詞的長度減一。也就是說,單詞 A 與單詞 B 押韻 當且僅當 LCS(A, B) ≥ max(|A|, |B|) – 1。(其中 LCS 是最長公共字尾 longest common suffix 的縮寫)
現在,Adrian 得到了 N 個單詞。他想從中選出儘可能多的單詞,要求它們能 組成一個單詞序列,使得單詞序列中任何兩個相鄰單詞是押韻的。

輸入

第一行是一個整數N。
接下來N行,每行一個由小寫英文字母組成的字串,表示每個單詞。所有單詞互不相同。

輸出

輸出一行,為一個整數,表示最長單詞序列的長度。

樣例輸入

5
ask
psk
k
krafna
sk

樣例輸出

4

提示

一種最長單詞序列是 ask-psk-sk-k。
30%的測試資料:1 ≤ N ≤ 20,所有單詞長度之和不超過 3 000。
100%的測試資料:1 ≤ N ≤ 500 000,所有單詞長度之和不超過 3 000 000。

來源/分類

【分析】

將所有單詞逆序,並建立字典樹,單詞結尾結點加標記。

問題就轉化為:

在字典樹上找一條最長的路徑,滿足 ①只走標記結點 ②從結點u可以走向父結點、孩子結點、兄弟結點。

設陣列 fdp[i] 表示以 i 結點為根節點時,可以從任一子樹走上來,剛走到 i 時的最長路徑(這是一條起始於子樹,終結於點 i 的路徑)。如圖藍色結點的藍色路徑為fdp表示最長路徑:

設陣列 dp[i] 表示以i結點為根節點時,把兩個擁有最長fdp[]的子樹連線起來,順便把所有孩子結點連上的最長路徑(這是一條從子樹起始,終結於子樹的路徑)。如圖粉紅色結點,把兩個擁有最大fdp的孩子連線到自己。

在dfs回溯過程中更新這兩個陣列即可。最後取陣列中的最大值即為答案

【程式碼】

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int MAX=1e6+5;

struct node{
    char ch;
    int next;
}edge[MAX];
int cnt,head[MAX],val[MAX]; //val[i]結點i結尾的單詞數
void init()
{
    memset(head,-1,sizeof(head));
    cnt=0;
}
void addedge(int u,char ch)
{
    edge[++cnt]=node{ch,head[u]};
    head[u]=cnt;
}
int new_word(char *str)
{
    int len=strlen(str);
    int u=0; //root node
    for(int i=0;i<len;i++)
    {
        int flag=1;
        for(int j=head[u];j!=-1;j=edge[j].next)
        {
            if(edge[j].ch==str[i])
            {
                flag=0;
                u=j;
                break;
            }
        }
        if(flag)
        {
            addedge(u,str[i]);
            u=cnt; //新節點
        }
    }
    val[u]++; //單詞結尾
}

int fdp[MAX]; //點u的由葉子上來的可走最長路徑
int dp[MAX]; //點u連線兩個孩子時最大值
int dfs(int u) //return點u的由葉子上來的可走最長路徑
{
    int mx[2]={0},mark=0;
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        dfs(i);
        int mxchild=fdp[i]-val[i]; //不包括i本身的最長鏈
        if(mxchild>mx[1]){mx[0]=mx[1];mx[1]=mxchild;}
        else if(mxchild>mx[0])mx[0]=mxchild;
        mark+=val[i];
    }
    mark+=val[u];
    if(mx[0]>0)dp[u]=mx[0]+mx[1]+mark; //連線兩孩子
    if(val[u])fdp[u]=mark+mx[1]; //最長單鏈
}

char str[MAX];
int main()
{
    int n;
    scanf("%d",&n);
    init();
    for(int i=0;i<n;i++)
    {
        scanf("%s",str);
        reverse(str,str+strlen(str));
        new_word(str);
    }
    dfs(0);
    int ans=0;
    for(int i=0;i<=cnt;i++)ans=max(ans,max(dp[i],fdp[i]));
    printf("%d\n",ans);
}