Description
- 原题GDSOI2019 D2 T3 Novel
Solution
- 相当巧妙的AC自动机上的DP。
- 首先对于这一类分数规划的问题一般都要二分答案,然后化一下式子变成,也就是长度每多1,贡献都要减去,求最大值。这样子就不需要记录起点了。
- 显然建一个AC自动机,直接设状态表示AC自动机上以为结尾的字符串最大的。
- 既然是在AC自动机上DP,那么考虑下一个字符的节点是,那么考虑的值,首先不难想到它直接从转移过来,但是这样还有一段起点不会考虑到。
- 因此再设一个状态表示从前为起点,到的最大值,那么
- 考虑的转移,设是的父亲,那么可以发现在跳fail的过程中,这些节点的所覆盖的起点区间刚好是不相交的,所以直接考虑将这些转移过来,再加上以当前位置为末尾的字符串的贡献。
- 这样一直跳到一个使得有儿子和匹配,即
- 这就是一个跳fail的过程,并且所有加进去的都包括,所以时间复杂度和正确性有了保证。
- 注意没有计算到整个串的贡献,单独补上。
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define maxn 300005
#define ll long long
#define db double
using namespace std;
int n,q,m,p,i,j,k,x,y,z;
int id,tot,tr[maxn][26],fail[maxn],dep[maxn];
db f[maxn],g[maxn],sum[maxn],h[maxn];
int t,w,d[maxn],fa[maxn];
void prepare(){
for(i=0;i<26;i++) tr[0][i]=1;
t=0,w=1,d[1]=1;
while (t<w){
x=d[++t];
for(i=0;i<26;i++) if (tr[x][i]){
y=tr[x][i],dep[y]=dep[x]+1,d[++w]=y;
for(z=fail[x];!tr[z][i]&&z;z=fail[z]);
fail[y]=tr[z][i];
sum[y]+=sum[fail[y]],h[y]=h[x]+sum[y];
}
}
}
int check(db mid){
t=0,w=1,d[1]=1;
for(i=1;i<=tot;i++) f[i]=g[i]=-1e15;
while (t<w){
x=d[++t];
for(i=0;i<26;i++) if (tr[x][i]){
y=tr[x][i];
g[y]=max(g[y],g[x]-mid+sum[fail[y]]);
for(z=fail[x];!tr[z][i]&&z;z=fail[z])
g[y]=max(g[y],g[z]-mid+sum[fail[y]]);
g[y]=max(g[y],h[y]-mid*dep[y]);
f[y]=max(f[fail[y]],g[y]);
d[++w]=y;
}
}
db mx=-1e15;
for(x=id;x;x=fa[x])
mx=max(mx,f[x]);
return mx>=0;
}
int main(){
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
char ch=getchar();
tot=x=1;
while (ch>='a'&&ch<='z') {
if (!tr[x][ch-'a']) tr[x][ch-'a']=++tot;
fa[tr[x][ch-'a']]=x,x=tr[x][ch-'a'],ch=getchar();
} id=x;
scanf("%d",&m);
while (m--){
for(ch=getchar();ch<'a'||ch>'z';ch=getchar());
x=1;
while (ch>='a'&&ch<='z') {
if (!tr[x][ch-'a']) tr[x][ch-'a']=++tot;
fa[tr[x][ch-'a']]=x,x=tr[x][ch-'a'],ch=getchar();
}
scanf("%d",&k),sum[x]+=k;
}
prepare();
db L=0,R=1e10,mid,E=1e-6;
while (L+E<R){
mid=(L+R)/2;
if (check(mid)) L=mid;
else R=mid;
}
printf("%.4lf",R);
}