[BJOI2019]奥术神杖

Posted by Dispwnl on April 23, 2019

题目

题解

因为是几个数相乘还开根,所以可以对每个值取$\ln$,这样把乘法化成了加法,开根号化成除法,设神力值(取$\lg$)的和为$T$,共有$n$个咒语,最终神力值为$t$,有

$T=t\times n$

一脸分数规划的样子,可以二分$t$,对每个值都$-t$

这样可以建出$AC$自动机在上面$dp$了,设$f_{i,j}$表示填到第$i$位转移到自动机第$j$个节点的最大值,分是否可填两种情况讨论即可,复杂度$O(10n\sum s_i\log \lg V)$?

代码

# include<iostream>
# include<cstring>
# include<cstdio>
# include<queue>
# include<cmath>
# include<algorithm>
# define X first
# define Y second
# define mp make_pair
# define Pi pair<int,char>
# define SON son[j][T[i]-'0']
using namespace std;
const int MAX=2e3+5;
const double fs=1e-6;
int n,m,tot;
double A;
double f[MAX][MAX];
char a[MAX],T[MAX],ans[MAX];
Pi tr[MAX][MAX];
struct AC{
	int fail[MAX],cnt[MAX];
	double val[MAX];
	int son[MAX][10];
	double ins(int _n,int V)
	{
		int x=0;
		for(int i=1;i<=_n;++i)
		  {
		  	if(!son[x][a[i]-'0']) son[x][a[i]-'0']=++tot;
		  	x=son[x][a[i]-'0'];
		  }
		return cnt[x]=1,val[x]=log(V);
	}
	void GET_FAIL()
	{
		queue<int> qu;
		for(int i=0;i<10;++i)
		  if(son[0][i]) qu.push(son[0][i]);
		while(!qu.empty())
		{
			int tt=qu.front();
			qu.pop();
			for(int i=0;i<10;++i)
			  if(son[tt][i]) fail[son[tt][i]]=son[fail[tt]][i],qu.push(son[tt][i]);
			  else son[tt][i]=son[fail[tt]][i];
			val[tt]+=val[fail[tt]],cnt[tt]+=cnt[fail[tt]];
		}
	}
	void Solve(int n,int pos)
	{
		if(!n) return;
		Solve(n-1,tr[n][pos].X),ans[n]=tr[n][pos].Y;
	}
	bool look(double mid)
	{
		for(int i=1;i<=tot;++i)
		  f[0][i]=-1e18;
		for(int i=1;i<=n;++i)
		  {
		  	for(int j=0;j<=tot;++j)
		  	  f[i][j]=-1e18;
		  	if(isdigit(T[i]))
		  	{
		  		for(int j=0;j<=tot;++j)
		  		  if(f[i-1][j]+val[SON]-mid*cnt[SON]>f[i][SON]) f[i][SON]=f[i-1][j]+val[SON]-mid*cnt[SON],tr[i][SON]=mp(j,T[i]);
			}
			else for(int j=0;j<=tot;++j)
			  for(int k=0;k<10;++k)
			    if(f[i-1][j]+val[son[j][k]]-mid*cnt[son[j][k]]>f[i][son[j][k]])
				f[i][son[j][k]]=f[i-1][j]+val[son[j][k]]-mid*cnt[son[j][k]],tr[i][son[j][k]]=mp(j,k+'0');
		  }
		double Mx=-1e18,pos=-1;
		for(int i=0;i<=tot;++i)
		  if(Mx<f[n][i]) Mx=f[n][i],pos=i;
		if(Mx<fs) return 0;
		return Solve(n,pos),1;
	}
}_A;
int main()
{
	scanf("%d%d%s",&n,&m,T+1);
	for(int i=1,v;i<=m;++i)
	  scanf("%s%d",a+1,&v),A=max(A,_A.ins(strlen(a+1),v));
	_A.GET_FAIL();
	double l=0,r=A;
	while(l<=r)
	{
		double mid=(l+r)/2;
		if(_A.look(mid)) l=mid+fs;
		else r=mid-fs;
	}
	return printf("%s",ans+1),0;
}