[Luogu5319][BJOI2019]奥术神杖(分数规划+AC自动机)

时间:2022-01-16 21:06:59

对最终答案取对数,得到$\ln(Ans)=\frac{1}{c}\sum \ln(v_i)$,典型的分数规划问题。
二分答案后,对所有咒语串建立AC自动机,然后套路地$f[i][j]$表示走到T的第i个字符,当前在自动机的第j个位置,能得到的最大收益。
注意二分的r初始不能设太大,25就可以了,二分终止的eps最好设到1e-5,否则会WA或者TLE。

 #include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=,inf=1e9;
const double eps=1e-;
char S[N],ss[N][N],pre[N][N];
int n,m,x,nd,sz[N],fail[N],q[N],son[N][],fr[N][N];
double v[N],w[N],f[N][N]; void add(char s[],double v){
int x=,len=strlen(s+);
rep(i,,len){
int c=s[i]-'';
if (!son[x][c]) son[x][c]=++nd;
x=son[x][c];
}
w[x]+=v; sz[x]++;
} void bfs(){
int st=,ed=;
rep(i,,) if (son[][i]) q[++ed]=son[][i];
while (st!=ed){
int x=q[++st];
rep(i,,) if (son[x][i]) fail[son[x][i]]=son[fail[x]][i],q[++ed]=son[x][i];
else son[x][i]=son[fail[x]][i];
w[x]+=w[fail[x]]; sz[x]+=sz[fail[x]];
}
} double solve(double mid){
rep(i,,n) rep(j,,nd) f[i][j]=-inf;
f[][]=; double ans=-inf;
rep(i,,n-) rep(j,,nd){
if (S[i+]!='.'){
int t=son[j][S[i+]-''];
if (f[i][j]+w[t]-mid*sz[t]>f[i+][t])
f[i+][t]=f[i][j]+w[t]-mid*sz[t],fr[i+][t]=j;
continue;
}
rep(c,,){
int t=son[j][c];
if (f[i][j]+w[t]-mid*sz[t]>f[i+][t])
f[i+][t]=f[i][j]+w[t]-mid*sz[t],fr[i+][t]=j,pre[i+][t]=c+'';
}
}
rep(i,,nd) ans=max(ans,f[n][i]); return ans;
} void Print(int i,int j){
if (!i) return;
Print(i-,fr[i][j]);
if (S[i]!='.') putchar(S[i]); else putchar(pre[i][j]);
} int main(){
freopen("arcana.in","r",stdin);
freopen("arcana.out","w",stdout);
scanf("%d%d%s",&n,&m,S+);
rep(i,,m) scanf("%s%d",ss[i]+,&x),v[i]=log(x),add(ss[i],v[i]);
double L=,R=; bfs();
while (L+eps<R){
double mid=(L+R)/;
if (solve(mid)>) L=mid; else R=mid;
}
solve(L); int mn=-inf,mnd=;
rep(i,,nd) if (f[n][i]>mn) mn=f[n][i],mnd=i;
Print(n,mnd);
return ;
}