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来判定如何缩小二分区间。

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

  • 相关阅读:
    第二十一章流 1流的操作 简单
    第二十章友元类与嵌套类 1友元类 简单
    第十九章 19 利用私有继承来实现代码重用 简单
    第二十章友元类与嵌套类 2嵌套类 简单
    第十九章 8链表类Node 简单
    第二十一章流 3用cin输入 简单
    第十九章 10 图书 药品管理系统 简单
    第十九章 11图书 药品管理系统 简单
    第二十一章流 4文件的输入和输出 简单
    第十九章 12 什么时候使用私有继承,什么时候使用包含 简单
  • 原文地址:https://www.cnblogs.com/cszmc2004/p/12781473.html
Copyright © 2011-2022 走看看