Trie——BZOJ4567/Luogu3294 [Scoi2016]背单词

http://www.lydsy.com/JudgeOnline/problem.php?id=4567
https://www.luogu.org/problem/show?pid=3294
比较有趣的Trie题目之一(而且还挺新的)
我们可以按照各串的后缀建一棵Trie树,然后dfs序遍历出需要的点并标记(指那些位于各串结尾的节点,这些是有用的)
接着我们来考虑题目中的三种情况:

  1. 如果存在一个单词是它的后缀,并且当前没有被填入表内,那他需要吃 n*n 颗泡椒才能学会;
    –这个因为代价太大,如果有这种情况绝对不是最优,不考虑
  2. 当它的所有后缀都被填入表内的情况下,如果在 1…x-1 的位置上的单词都不是它的后缀,那么你吃 x 颗泡椒就能记住它;
    –这个需要考虑
  3. 当它的所有后缀都被填入表内的情况下,如果 1…x-1的位置上存在是它后缀的单词,所有是它后缀的单词中,序号最大为 y ,那么你只要吃 x-y 颗泡椒就能把它记住。
    –这个需要考虑

因为不考虑1,即每个后缀填完后才能填自己,同时呢填完一个单词的子树的最小代价是固定的(自己yy一下
所以只要合理安排儿子的遍历顺序即可。
这题就转化成了贪心
显然的,我们就应该先遍历子树较小的儿子,这样总代价最小(贪心嘛)

#include
#define ll long long
using namespace std;
ll ans=0;
int s[500010],fa[5000010],a[500010],h[500010],son[500010];
char c[1000001];
bool b[500001]={0};
int n,p[500010][26]={0},np=1;
inline void insert(int x){
    int now=1,l=strlen(c+1);
    for(int i=l;i;i--){
        int t=c[i]-'a';
        if(!p[now][t])p[now][t]=++np;
        now=p[now][t];s[now]++;
    }
    b[now]=1;
}
inline void dfs(int x){
    int father=fa[x];
    if(b[x]){
        h[x]=a[fa[x]];a[fa[x]]=x;father=x;
    }
    for(int i=0;i<26;i++)if(p[x][i]){
        fa[p[x][i]]=father;dfs(p[x][i]);
    }
}
inline void getans(int x){
    int sum=0;
    for(int i=a[x];i;i=h[i])sum++,son[sum]=s[i];
    sort(son+1,son+sum+1);
    s[x]=1;
    for(int i=1;i<=sum;i++)ans+=s[x],s[x]+=son[i];
    for(int i=a[x];i;i=h[i])getans(i);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%s",c+1);
        insert(i);
    }
    fa[1]=1;dfs(1);getans(1);
    printf("%lld",ans);
    return 0;
}

你可能感兴趣的:(贪心,Trie/AC自动机)