标签:lang 最大值 ac自动机 就是 fail stdout print 转化 开始
有个长度为\(n\)的\(A\)串和若干个串\(B_i\),\(B_i\)的价值为\(v_i\)
若\(A_{l..r}=B_i\),则所有满足\([l,r]\in[L,R]\)的区间\([L,R]\)会得到\(v_i\)的价值。
记\(W_{L,R}\)为区间\([L,R]\)的价值。
求\(\max \frac{W_{L,R}}{R-L+1}\)
\(n\leq1e5\)
\(\sum_{B_i}\leq2e5\)
思考了半天,最终发现自己根本没有思考出什么有实际意义的东西。
想着如何用一些值来表示区间的贡献,但是却一直搞不出来。
最终\(O(n^2)\)拿\(30\)分走人……
(然而由于调试输出没有关爆零了)
我竟然没想到第一步
看着这个平均数是不是觉得太丑……
于是\(0/1\)分数规划一下,二分\(ans\),于是变成了求最大的\(W_{L,R}-(R-L+1)ans\)
如果大于等于\(0\)就合法。
转化问题之后的式子好看很多,这相当于是每多一个长度就要新增\(ans\)的贡献。
后面讲述时“答案”定义为\(W_{L,R}-(R-L+1)ans\)的最大值。
正解是真的神仙。
首先,将\(A\)串和\(B\)串丢入AC自动机中。
维护\(f_x\)表示根到节点\(x\)形成的字符串中,最大的以\(x\)为右端点的区间的价值。
于是需要计算所有左端点的贡献。
记\(i\)为这个字符串的最后一个位置。
注意到\(f_{fail_x}\)包括了左端点为\([i-len_{fail_x}+1,i]\)的答案,于是\(f_{fail_x}\)可以直接转移到\(f_x\)。
接下来考虑在\([1,i-len_{fail_x}]\)的左端点的答案。
定义\(g_x\)表示左端点在\([1,i-len_{fail_x}]\),右端点为\(i\)的答案。
\(g_{fa_x}\)为左端点在\([1,i-1-len_{fail_{fa_x}}]\),右端点为\(i-1\)的答案。于是\(g_{fa_x}+v_{fail_x}-ans\)就是左端点在\([1,i-1-len_{fail_{fa_x}}]\)的时候的贡献。
(\(v_x\)为\(fail\)树上字符串价值的前缀和)
剩余的区间呢?
联想一下\(x\)求\(fail\)的过程,从\(fail_{fa_x}\)开始,跳若干条\(fail\)边,直到找到为止。
设\(y\)为从\(fa_x\)开始,跳\(fail\)的过程中经过的点。
显然\(y\)在跳\(fail\)的过程中,\(len_{fail_y}\)在一直在缩水。考虑\(g_y\)是什么,就是从之前某个地方跳\(fail\)跳到\(y\)的时候,左端点位于缩水的那一部分的贡献。
写出来贡献就是\(g_y+v_{fail_x}-ans\)
最终\(y\)会变成\(fa_{fail_x}\),可以发现缩水的区间正好就是\([1,i-len_{fail_x}]\)
然后\(g_x\)和\(f_{fail_x}\)的贡献就可以覆盖所有的情况。
注意转移\(g_x\)的时候还要另外计算一下选择整个区间的情况。
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cassert>
#define N 200010
#define ll long long
char a[N],b[N];
struct Node{
Node *c[26],*fail;
int dep;
ll v,s;
double f,g;
} d[N*2],*rt;
int cnt;
void insert(char *s,int w){
Node *t=rt;
for (;*s;++s){
if (!t->c[*s-‘a‘])
t->c[*s-‘a‘]=&d[++cnt];
t=t->c[*s-‘a‘];
}
t->v+=w;
}
Node *q[N*2];
void init(){
int head=1,tail=1;
q[1]=rt;
rt->fail=rt;
while (head<=tail){
Node *t=q[head++];
for (int i=0;i<26;++i)
if (t->c[i]){
if (t==rt)
t->c[i]->fail=rt;
else{
Node *p=t->fail;
while (p!=rt && !p->c[i])
p=p->fail;
if (p->c[i])
t->c[i]->fail=p->c[i];
else
t->c[i]->fail=rt;
}
t->c[i]->v+=t->c[i]->fail->v;
t->c[i]->s=t->c[i]->v+t->s;
t->c[i]->dep=t->dep+1;
q[++tail]=t->c[i];
}
}
}
bool ok(double lim){
int head=1,tail=1;
q[1]=rt;
rt->fail=rt;
rt->f=rt->g=-1e16;
while (head<=tail){
Node *t=q[head++];
for (int i=0;i<26;++i)
if (t->c[i]){
if (t==rt)
t->c[i]->f=t->c[i]->g=t->c[i]->s-lim*t->c[i]->dep;
else{
t->c[i]->g=t->g-lim+t->c[i]->fail->v;
Node *p=t->fail;
while (p!=rt && !p->c[i]){
t->c[i]->g=max(t->c[i]->g,p->g-lim+t->c[i]->fail->v);
p=p->fail;
}
t->c[i]->g=max(t->c[i]->g,t->c[i]->s-lim*t->c[i]->dep);
t->c[i]->f=max(t->c[i]->fail->f,t->c[i]->g);
}
q[++tail]=t->c[i];
}
}
Node *t=rt;
for (char *ch=a;*ch;++ch){
t=t->c[*ch-‘a‘];
if (t->f>=0)
return 1;
}
return 0;
}
int main(){
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
int K;
scanf("%s%d",a,&K);
rt=&d[cnt=1];
insert(a,0);
for (int i=1;i<=K;++i){
int w;
scanf("%s%d",b,&w);
insert(b,w);
}
Node *t=rt;
init();
double l=0,r=1e10;
while (r-l>1e-5){
double mid=(l+r)/2;
if (ok(mid))
l=mid;
else
r=mid;
}
printf("%.4lf\n",r);
return 0;
}
神仙题……
据说当年samjia都没有切掉……
标签:lang 最大值 ac自动机 就是 fail stdout print 转化 开始
原文地址:https://www.cnblogs.com/jz-597/p/13052645.html