[BJOI2019]奥术神杖——AC自动机+DP+分数规划+二分答案

时间:2021-04-29 21:06:58

题目链接:

[BJOI2019]奥术神杖

答案是$ans=\sqrt[c]{\prod_{i=1}^{c}v_{i}}=(\prod_{i=1}^{c}v_{i})^{\frac{1}{c}}$。

这样不大好求,我们将这个式子取$ln$,变成$ln\ ans=\frac{1}{c}\sum_{i=1}^{c}ln\ v_{i}$。

这显然是一个分数规划,每次二分一个答案$mid$,将每个串的权值都减去$mid$,那么只需要求最大价值是否大于$0$即可。

剩下的问题就是一个在$AC$自动机上的$DP$了,设$f[i][j]$表示在$AC$自动机上的点$j$,已经匹配的长度为$i$时的最大值,在$AC$自动机上转移即可。

在$DP$时还要记录一下每个状态从哪个点转移过来以便输出方案。

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<cstdio>
#include<vector>
#include<bitset>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const double eps=1e-5;
int end[2000];
double f[2000][2000];
int g[2000][2000];
int fail[2000];
int tr[2000][10];
int cnt;
double val[2000];
char ch[2000];
char s[2000];
int now;
int n,m;
int v;
double ans;
void build(char *ch,int v)
{
int len=strlen(ch);
now=0;
for(int i=0;i<len;i++)
{
int x=ch[i]-'0';
if(!tr[now][x])
{
tr[now][x]=++cnt;
}
now=tr[now][x];
}
end[now]++;
val[now]+=log(v);
}
void get_fail()
{
queue<int>q;
for(int i=0;i<=9;i++)
{
if(tr[0][i])
{
q.push(tr[0][i]);
}
}
while(!q.empty())
{
now=q.front();
q.pop();
val[now]+=val[fail[now]];
end[now]+=end[fail[now]];
for(int i=0;i<=9;i++)
{
if(tr[now][i])
{
fail[tr[now][i]]=tr[fail[now]][i];
q.push(tr[now][i]);
}
else
{
tr[now][i]=tr[fail[now]][i];
}
}
}
}
double DP(double mid)
{
for(int i=0;i<=n;i++)
{
for(int j=0;j<=cnt;j++)
{
f[i][j]=-1e9;
g[i][j]=0;
}
}
f[0][0]=0;
for(int i=0;i<n;i++)
{
for(int j=0;j<=cnt;j++)
{
if(f[i][j]>-1e9)
{
if(s[i+1]=='.')
{
for(int k=0;k<=9;k++)
{
if(f[i+1][tr[j][k]]<f[i][j]+val[tr[j][k]]-mid*end[tr[j][k]])
{
f[i+1][tr[j][k]]=f[i][j]+val[tr[j][k]]-mid*end[tr[j][k]];
g[i+1][tr[j][k]]=j;
}
}
}
else
{
int x=s[i+1]-'0';
if(f[i+1][tr[j][x]]<f[i][j]+val[tr[j][x]]-mid*end[tr[j][x]])
{
f[i+1][tr[j][x]]=f[i][j]+val[tr[j][x]]-mid*end[tr[j][x]];
g[i+1][tr[j][x]]=j;
}
}
}
}
}
double res=-1e9;
for(int i=0;i<=cnt;i++)
{
res=max(res,f[n][i]);
}
return res;
}
void print(int dep,int now)
{
if(!dep)
{
return ;
}
print(dep-1,g[dep][now]);
if(s[dep]!='.')
{
printf("%c",s[dep]);
return ;
}
else
{
for(int i=0;i<=9;i++)
{
if(tr[g[dep][now]][i]==now)
{
printf("%c",i+'0');
return ;
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
scanf("%s",s+1);
for(int i=1;i<=m;i++)
{
scanf("%s%d",ch,&v);
build(ch,v);
}
get_fail();
double l=0,r=22;
while(r-l>eps)
{
double mid=(l+r)/2;
if(DP(mid)>0)
{
l=mid;
ans=mid;
}
else
{
r=mid;
}
}
DP(ans);
for(int i=0;i<=cnt;i++)
{
if(f[n][i]>0)
{
print(n,i);
break;
}
}
}