[BJOI2019]奥术神杖(分数规划+AC自动机+DP)
题解:很显然可以对权值取对数,然后把几何平均值转为算术平均值,然后很显然是分数规划。先对每个模式串建立AC自动机,每个节点w[i],sz[i]分别表示以其为前缀的字符串,然后再二分最优解k,然后w[i]-=k*sz[i],然后枚举T,在AC自动机上DP一遍,求最大值是否大于0即可。
#include<bits/stdc++.h> using namespace std; const int N=1555; int n,m,tot,ch[N][10],fail[N],sz[N],g[N][N],h[N][N]; double w[N],f[N][N]; char T[N],str[N],ans[N]; void insert(int v) { int u=0,len=strlen(str+1); for(int i=1;i<=len;++i) { if(!ch[u][str[i]-'0'])ch[u][str[i]-'0']=++tot; u=ch[u][str[i]-'0']; } w[u]=log(v),sz[u]+=1; } void build() { queue<int>q; for(int i=0;i<10;i++)if(ch[0][i])q.push(ch[0][i]); while(!q.empty()) { int u=q.front();q.pop(); w[u]+=w[fail[u]],sz[u]+=sz[fail[u]]; for(int i=0;i<10;i++) if(ch[u][i])fail[ch[u][i]]=ch[fail[u]][i],q.push(ch[u][i]); else ch[u][i]=ch[fail[u]][i]; } } void dp(int i,int j,int k) { int c=ch[j][k]; if(f[i][c]<f[i-1][j]+w[c])f[i][c]=f[i-1][j]+w[c],g[i][c]=j,h[i][c]=k; } bool check(double val) { for(int i=0;i<=tot;i++)w[i]-=val*sz[i]; for(int i=0;i<=n;i++) for(int j=0;j<=tot;j++) f[i][j]=-1e300; f[0][0]=0; for(int i=1;i<=n;i++) for(int j=0;j<=tot;j++) if(T[i]=='.')for(int k=0;k<10;k++)dp(i,j,k); else dp(i,j,T[i]-'0'); double ans=-1e300; for(int i=1;i<=tot;i++)ans=max(ans,f[n][i]); for(int i=0;i<=tot;i++)w[i]+=val*sz[i]; return ans>0; } int main() { scanf("%d%d",&n,&m); scanf("%s",T+1); for(int i=1,x;i<=m;i++)scanf("%s%d",str+1,&x),insert(x); build(); double l=0,r=25,mid; while(r-l>1e-3) { mid=(l+r)/2; if(check(mid))l=mid;else r=mid; } check(l); int pos=0; for(int i=1;i<=tot;i++)if(f[n][i]>f[n][pos])pos=i; for(int i=n;i;i--)ans[i]=h[i][pos]+48,pos=g[i][pos]; for(int i=1;i<=n;i++)putchar(ans[i]); }