[SCOI2016][JZOJ4631]背单词

题目大意

题目大意
1n105,1|S|5.1×105


题目分析

题意差评。
显然最优解下,第一种情况我们显然不会让它发生。
如果我们将字符串之间的后缀关系连成一棵树,那么可以发现此题相当于给树分配一种序列,满足祖先必须在儿子之前,并且儿子与父亲位置差之和最小。
我们可以将所有串反过来构造一棵 Trie 解决连边问题(然而弱智的我考场上敲了个 AC 自动机,用 fail 树乱搞)。
考虑贪心策略,这题让我想到了小学奥数经常考的排队打水问题,要求等待时间之和尽量小,显然这两个问题是差不多的。因此我们可以先递归处理子树,然后将子树按 size 从小到大排序,依次分配子树根节点,这样一定最优。证明就大家自己细想吧,我是感性理解的~
时间复杂度 O(Llog2L)


代码实现

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>

using namespace std;

typedef long long LL;

const int N=100050;
const int L=510050;
const int C=26;

int size[L],son[L];
LL f[L];
int n,l;

struct tree
{
    int next[L],tov[L],last[L];
    int tot;

    void insert(int x,int y){tov[++tot]=y,next[tot]=last[x],last[x]=tot;}
}ft,t;

char s[L];

struct AC_automaton
{
    int next[L][C],fail[L];
    queue<int> q;
    bool word[L];
    int tot,root;

    int newnode()
    {
        ++tot;
        for (int c=0;c<C;c++) next[tot][c]=-1;
        return tot;
    }

    void init()
    {
        tot=-1;
        root=newnode();
    }

    void insert()
    {
        int rt=root;
        l=strlen(s);
        for (int i=0;i<l;i++)
        {
            if (next[rt][s[i]-'a']==-1) next[rt][s[i]-'a']=newnode();
            rt=next[rt][s[i]-'a'];
        }
        word[rt]=true;
    }

    void build()
    {
        for (int c=0;c<C;c++)
            if (next[root][c]==-1) next[root][c]=0;
            else fail[next[root][c]]=0,q.push(next[root][c]);
        for (int x,c;!q.empty();)
        {
            x=q.front(),q.pop();
            for (c=0;c<C;c++)
                if (next[x][c]!=-1) fail[next[x][c]]=next[fail[x]][c],q.push(next[x][c]);
                else next[x][c]=next[fail[x]][c];
        }
        for (int x=1;x<=tot;x++) ft.insert(fail[x],x);
    }

    void dfs(int x,int fa)
    {
        if (word[x]) t.insert(fa,x);
        for (int i=ft.last[x];i;i=ft.next[i]) dfs(ft.tov[i],word[x]?x:fa);
    }
}ACam;

bool cmp(int x,int y){return size[x]<size[y];}

void calc(int x)
{
    size[x]=1;
    for (int i=t.last[x],y;i;i=t.next[i])
        calc(y=t.tov[i]),size[x]+=size[y];
    son[0]=0;
    for (int i=t.last[x];i;i=t.next[i])
        son[++son[0]]=t.tov[i];
    sort(son+1,son+1+son[0],cmp);
    for (int i=1,cnt=1;i<=son[0];i++) f[x]+=f[son[i]]+cnt,cnt+=size[son[i]];
}

int main()
{
    freopen("word.in","r",stdin),freopen("word.out","w",stdout);
    ACam.init();
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
    {
        scanf("%s",s);
        ACam.insert();
    }
    ACam.build();
    ACam.dfs(0,0);
    calc(0);
    printf("%lld\n",f[0]);
    fclose(stdin),fclose(stdout);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值