JZOJ6682. 【2020.06.04省选模拟】串在哪(string)

49 篇文章 0 订阅
2 篇文章 0 订阅

Description

在这里插入图片描述
n < = 1 0 5 , ∑ ∣ B i ∣ < = 2 e 5 n<=10^5,\sum|Bi|<=2e5 n<=105,Bi<=2e5

  • 原题GDSOI2019 D2 T3 Novel

Solution

  • 相当巧妙的AC自动机上的DP。
  • 首先对于这一类分数规划的问题一般都要二分答案,然后化一下式子变成 W l , r − m i d ∗ l e n > = 0 W_{l,r}-mid*len>=0 Wl,rmidlen>=0,也就是长度每多1,贡献都要减去 m i d mid mid,求最大值。这样子就不需要记录起点了。
  • 显然建一个AC自动机,直接设状态 f [ i ] f[i] f[i]表示AC自动机上以 i i i为结尾的字符串最大的 W l , r − m i d ∗ l e n W_{l,r}-mid*len Wl,rmidlen
  • 既然是在AC自动机上DP,那么考虑下一个字符的节点是 x x x,那么考虑 f [ x ] f[x] f[x]的值,首先不难想到它直接从 f [ f a i l [ x ] ] f[fail[x]] f[fail[x]]转移过来,但是这样还有一段起点不会考虑到。
  • 因此再设一个状态 g [ x ] g[x] g[x]表示从 f a i l [ x ] fail[x] fail[x]前为起点,到 x x x的最大值,那么 f [ x ] = m a x ( g [ x ] , f [ f a i l [ x ] ] ) f[x]=max(g[x],f[fail[x]]) f[x]=max(g[x],f[fail[x]])
  • 考虑 g [ x ] g[x] g[x]的转移,设 y y y x x x的父亲,那么可以发现在 y y y跳fail的过程中,这些节点 f a i l [ y ] , f a i l [ f a i l [ y ] ] , f a i l [ . . . ] fail[y],fail[fail[y]],fail[...] fail[y],fail[fail[y]],fail[...] g g g所覆盖的起点区间刚好是不相交的,所以直接考虑将这些 g g g转移过来,再加上以当前位置为末尾的字符串的贡献 s u m [ f a i l [ x ] ] − m i d sum[fail[x]]-mid sum[fail[x]]mid
  • 这样一直跳到一个 y y y使得 y y y有儿子 z z z x x x匹配,即 f a i l [ x ] = z fail[x]=z fail[x]=z
  • 这就是一个跳fail的过程,并且所有加进去的 g g g都包括 f a i l [ x ] fail[x] fail[x],所以时间复杂度和正确性有了保证。
  • 注意没有计算到整个串 x x x的贡献,单独补上。
#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);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值