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

原文地址:https://www.cnblogs.com/ctmlpfs/p/13743367.html