6682. 2020.06.04省选模拟串在哪
Posted jz-597
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了6682. 2020.06.04省选模拟串在哪相关的知识,希望对你有一定的参考价值。
题目
有个长度为(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})
(nleq1e5)
(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
",r);
return 0;
}
总结
神仙题……
据说当年samjia都没有切掉……
以上是关于6682. 2020.06.04省选模拟串在哪的主要内容,如果未能解决你的问题,请参考以下文章