cup 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。

来源/分类

2018北京冬令营1 

【分析】

将所有单词逆序,并建立字典树,单词结尾结点加标记。

问题就转化为:

在字典树上找一条最长的路径,满足 ①只走标记结点 ②从结点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);
}

猜你喜欢

转载自blog.csdn.net/winter2121/article/details/81213624
CUP