• [GDOI2019]小说


    本人从自己已注销的前博客内搬运一篇文章。
    这道题的60分做法是:分答案串(<=100)(>100)讨论。
    (<=100)的部分可以使用算法3解决。枚举一个点l表示现在要统计左端点l~l+99所有前缀字符串的密度的最大值。可以使用ac自动机解决
    (>100)的部分可以二分+ac自动机。
    原问题实际上是一个分数规划问题。假设选了i个物品,编号a[1],a[2]...a[i],则平均值是(frac{a[1]+a[2]+....+a[i]}{i})
    如果设一个二分值mid,判定答案是否>=mid,则实际上是判定(a[1]-md+a[2]-md+...+a[i]-md>=0),实际上就是选一个位置有md的代价,要求最大值。
    使用dp解决。设f[i]表示i结尾的串,长度要>100的最大价值。ans[i]表示i-99~i的以i结尾的子串价值的最大值,st[i]表示ac机上以i结尾的串的价值,ans可以使用算法3计算。
    f的转移有点像最大子段和。设v表示1~i-100的前缀最大价值,则v=max(v+st[i-100]-md,0),表示枚举是否选i-100这个点。
    f[i]=max(f[i],v+ans[i]-100*md)表示使用i-99i和1i-100的最大价值组合更新答案。
    只需要判定max(f[1~n])是否>=0即可。
    核心代码:

    int ck(double md){
        double r=-1e18,v=0;
        for(int i=100;i<n;i++){
            v=max(v+st[i-100]-md,0.0);
            r=max(r,v+ans[i]-100.0*md);
        }
        return r>=0;
    }
    double va=0;
    for(int i=0;i<n;i++){
        int x=0,ss=0;
        for(int j=i;j<min(i+100,n);j++){
            x=a.c[x][s[j]-'a'];
            ss+=a.f[x];
            va=max(va,ss/((double)j-i+1));
        }
        if(i+99<n)ans[i+99]=ss;
    }
    int x=0;
    for(int i=n-1;i;i--){
        x=b.c[x][s[i]-'a'];
        st[i]=b.f[x];
    }
    double l=0,r=1000000000;
    while(l+1e-6<r){
        double md=(l+r)*0.5;
        if(ck(md))l=md;
        else r=md;
    }
    printf("%.4lf",max(l,va));
    

    满分做法也是要二分(0/1分数规划)。
    将原串在ac机上匹配。
    我们假设在匹配的时候匹配到了一个字符c,且当前匹配点没有c的儿子,则跳fail。直到有c位置。
    答案就是max(当前匹配串~i 后缀最大贡献,当前匹配串外~i 后缀最大贡献),和60分做法较为相似。
    设mx[p]表示ac自动机上,p对应的字符串的所有后缀的贡献的最大值。
    f[p]表示ac机上[当前~fail]到i的贡献最大值。
    g[p]表示根到p对应的字符串的分数和。
    fa[p]表示p的失配指针。
    计算f,g时在ac自动机上按照bfs序进行遍历。
    设当前bfs到的节点为x。我们接下来要计算x在trie的儿子y的f,g值。
    计算g时直接把x+分数和赋值给g[y]即可。
    计算f时,我们要跳到一个点,使得这个点存在y对应的字符。
    在跳的时候,我们发现原来的值是(lasfail)(las表示这次跳前对应的字符串的首位),跳之后我们对应的是failffail(ffail时现在跳到的点)。发现这2个值恰好可以覆盖整个字符串区间。
    所以对沿途的所有f取max即可。
    然后把f对节点g的值取max,表示插入当前y对应的字符后,有新的贡献是g[y]前面没有统计到,要取max。
    在统计答案时,统计的是现在枚举到的位置i的后缀的最大贡献。
    匹配到的部分的后缀最大值就是mx[p],p为当前串在ac机上匹配到的节点。
    匹配到的位置前面的部分要使用一个变量pp记录,表示前面的最大分数和。
    在跳fail时,我们要计算前面的贡献,所以要将pp对沿途的f[p]取max。
    然后在跳完以后,pp加上现在这个节点的分数和。
    把答案对max(mx[p],pp)取max即可,就是对max(当前匹配串~i 后缀最大贡献,当前匹配串外~i 后缀最大贡献)取max
    然后根据答案是否>=0来判定如何缩小二分区间。

  • 相关阅读:
    ASP.NET初识4
    属性
    ASP.NET初识4
    ACCP6.0第九章练习
    ASP.NET初识1
    鼠标指针含义
    ASP.NET初识2
    第三部分
    ASP.NET初识3
    ASP.NET初识5
  • 原文地址:https://www.cnblogs.com/ctmlpfs/p/13743367.html
Copyright © 2020-2023  润新知