CodeForces 965E Short Code (字典树的缩树)

Arkady's code contains nn variables. Each variable has a unique name consisting of lowercase English letters only. One day Arkady decided to shorten his code.

He wants to replace each variable name with its non-empty prefix so that these new names are still unique (however, a new name of some variable can coincide with some old name of another or same variable). Among such possibilities he wants to find the way with the smallest possible total length of the new names.

A string aa is a prefix of a string bb if you can delete some (possibly none) characters from the end of bb and obtain aa.

Please find this minimum possible total length of new names.

Input

The first line contains a single integer nn (1n1051≤n≤105) — the number of variables.

The next nn lines contain variable names, one per line. Each name is non-empty and contains only lowercase English letters. The total length of these strings is not greater than 105105. The variable names are distinct.

Output

Print a single integer — the minimum possible total length of new variable names.

Examples
Input
3
codeforces
codehorses
code
Output
6
Input
5
abba
abb
ab
aa
aacada
Output
11
Input
3
telegram
digital
resistance
Output
3
Note

In the first example one of the best options is to shorten the names in the given order as "cod", "co", "c".

In the second example we can shorten the last name to "aac" and the first name to "a" without changing the other names.


【题意】:

给出n个单词,每个单词可以用自己的前缀表示,但这个前缀是唯一的,不能与其他单词的前缀表示重复。

问,把所有单词用前缀表示后,最少的字母数量。

【分析】

建立字典树,单词的结尾结点标记1,然后dfs这棵树。

当前位于结点rt,若rt结点没有标记,则可以从其子树中选择一个最深的结点,转移给自己(即单词前缀)把自己变成这个单词的结尾。按照此贪心策略dfs回溯,就能使得所有结点尽量提升,而且所有结点深度之和最小。

【代码】

#include<bits/stdc++.h>
using namespace std;

/******** 字典树 *********/
const int MAX=26;
struct node{
    char ch; ///root =0,root.ch=-1;
    int deep;
    int total;
    int next[MAX];
}nod[202020];
int cnt;
void init(int rt=0)  ///根节点初始化
{
    cnt=0;
    nod[rt].deep=0;
    nod[rt].total=0;
    for(int i=0;i<MAX;i++)nod[rt].next[i]=-1;
}
void innew(char s[],int rt=0)  ///插入新单词
{
    if(strlen(s)==0)
    {
        nod[rt].total++;
        return;
    }
    int son=s[0]-'a';
    if(nod[rt].next[son]==-1)
    {
        nod[rt].next[son]=++cnt; //申请子节点

        nod[cnt].deep=nod[rt].deep+1;
        nod[cnt].total=0;
        for(int i=0;i<MAX;i++)nod[cnt].next[i]=-1;
    }
    innew(s+1,nod[rt].next[son]);
}

/****** 优先队列(深度) ******/
struct cmp{
    bool operator()(int a,int b)
    {
        return nod[a].deep<nod[b].deep;
    }
};
priority_queue<int,vector<int>,cmp>q[202020];

/******* dfs字典树缩树(把使得树的孩子节点尽量往根缩) *****/
void dfs(int rt)
{
    if(rt==-1)return;
    while(!q[rt].empty())q[rt].pop(); ///进入结点先清空
    for(int i=0;i<MAX;i++)
    {
        int son=nod[rt].next[i];
        dfs(son);
        while(!q[son].empty()){q[rt].push(q[son].top());q[son].pop();} ///把子节点的队列给父节点
    }
    if(rt!=0&&nod[rt].total==0&&!q[rt].empty()) ///拿一个最深的子节点放在rt
    {
        nod[rt].total++;
        nod[q[rt].top()].total--;
        q[rt].pop();
    }
    if(nod[rt].total==1)
        q[rt].push(rt);
}

int main()
{
    ios::sync_with_stdio(0);
    int n;
    string s[202020];
    while(cin>>n)
    {
        init();
        for(int i=0;i<n;i++){
            cin>>s[i];
            innew((char*)s[i].c_str());
        }
        dfs(0);

        int sum=0;
        while(!q[0].empty()) ///根结点的优先队列保存了缩树之后的所有结点地址
        {
            sum+=nod[q[0].top()].deep;
            q[0].pop();
        }
        cout<<sum<<endl;
    }
}


猜你喜欢

转载自blog.csdn.net/winter2121/article/details/80674322