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省选模拟串在哪的主要内容,如果未能解决你的问题,请参考以下文章

6687. 2020.06.04省选模拟树没了(tree)

6687. 2020.06.04省选模拟树没了(tree)

[考试反思]0410省选模拟67:迷惑

[考试反思]0131省选模拟测14:遗失

省选模拟(66~70)

5.10 省选模拟赛 拍卖 博弈 dp