zoukankan      html  css  js  c++  java
  • 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-99~i和1~i-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对应的字符。

    在跳的时候,我们发现原来的值是(las~fail)(las表示这次跳前对应的字符串的首位),跳之后我们对应的是fail~ffail(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来判定如何缩小二分区间。

    感觉这题的做法十分巧妙。

  • 相关阅读:
    有个表叫杨表(上)
    Codeforces Round #698 (Div. 2) 题解 全部6题
    Leetcode 821. 字符的最短距离
    gitbook mermaid不能渲染问题
    adb命令启动app及查找系统版本号
    git库使用
    excle转html方法
    gitbook插入视频
    xcode使用技巧
    在 Mac 上的“自动操作”工作流程中使用 Shell 脚本操作
  • 原文地址:https://www.cnblogs.com/cszmc2004/p/12781473.html
Copyright © 2011-2022 走看看