luoguP5319 [BJOI2019]奥术神杖 AC自动机+01分数规划+dp
显然取对数,然后二分答案进行 01 分数规划.
设 $f[i][j]$ 表示在 AC 自动机上的点 $i$ ,匹配到了 $j$ 位的最大价值.
转移的时候判断一下当前是点还是数字,然后在 AC 自动机上的终止节点上算一下贡献就行.
构建 AC 自动机的时候要注意:点 $i$ 的价值是 val[i]+i祖先的贡献和.
然后 01 分数规划的时候要注意精度,且要判断 f[n][i] 是否大于 0,不能直接判 f[j][i].
code:
#include <bits/stdc++.h> #define N 1608 #define inf 100000 #define ll long long #define setIO(s) freopen(s".in","r",stdin) using namespace std; const double eps=1e-7; struct data { int ch[10],f,fl,sum; double det; }s[N]; queue<int>q; double f[N][N]; char str[N],ar[N]; int tot,n,m,loc[N],pre[N][N],col[N][N]; void ins(int d) { int x=0,y,z,c; scanf("%s%d",ar+1,&z),y=strlen(ar+1); for(int i=1;i<=y;++i) { c=ar[i]-'0'; if(!s[x].ch[c]) s[x].ch[c]=++tot; x=s[x].ch[c]; } s[x].fl=1,s[x].det+=log(z),loc[d]=x,s[x].sum++; } void build() { for(int i=0;i<10;++i) if(s[0].ch[i]) q.push(s[0].ch[i]); while(!q.empty()) { int u=q.front(); q.pop(); for(int i=0;i<10;++i) { if(!s[u].ch[i]) { s[u].ch[i]=s[s[u].f].ch[i]; continue; } int v=s[u].ch[i]; s[v].f=s[s[u].f].ch[i]; s[v].sum+=s[s[v].f].sum; s[v].det+=s[s[v].f].det; q.push(v); } } } int check(double t) { int x,y,z=0,c; for(int i=1;i<=tot;++i) s[i].det-=s[i].sum*t; for(int i=0;i<=n;++i) for(int j=0;j<=tot;++j) f[i][j]=-inf; f[0][0]=0; for(int i=0;i<n;++i) { for(int j=0;j<=tot;++j) { if(str[i+1]=='.') { for(c=0;c<10;++c) { if(f[i][j]+s[s[j].ch[c]].det>f[i+1][s[j].ch[c]]) { f[i+1][s[j].ch[c]]=f[i][j]+s[s[j].ch[c]].det; pre[i+1][s[j].ch[c]]=j; col[i+1][s[j].ch[c]]=c; } } } else { c=str[i+1]-'0'; if(f[i][j]+s[s[j].ch[c]].det>f[i+1][s[j].ch[c]]) { f[i+1][s[j].ch[c]]=f[i][j]+s[s[j].ch[c]].det; pre[i+1][s[j].ch[c]]=j; col[i+1][s[j].ch[c]]=c; } } } } for(int i=0;i<=tot;++i) if(f[n][i]>0) z=1; for(int i=1;i<=tot;++i) s[i].det+=s[i].sum*t; return z; } void print(int x,int l) { if(l>1) print(pre[l][x],l-1); printf("%d",col[l][x]); } int main() { // setIO("input"); scanf("%d%d%s",&n,&m,str+1); for(int i=1;i<=m;++i) ins(i); build(); // 注意这里 01 分数规划的写法 !! double l=0.0,r=log(1e9+9),mid,ans; while(r-l>=eps) { mid=(l+r)*0.5; if(check(mid)) l=mid; else r=mid; } check(l); for(int i=0;i<=tot;++i) if(f[n][i]>0) { print(i,n); break; } return 0; }