码迷,mamicode.com
首页 > 其他好文 > 详细

6682. 【2020.06.04省选模拟】串在哪

时间:2020-06-05 23:01:10      阅读:58      评论:0      收藏:0      [点我收藏+]

标签: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都没有切掉……

6682. 【2020.06.04省选模拟】串在哪

标签:lang   最大值   ac自动机   就是   fail   stdout   print   转化   开始   

原文地址:https://www.cnblogs.com/jz-597/p/13052645.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!