[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]);
}
View Code

猜你喜欢

转载自www.cnblogs.com/hfctf0210/p/10747650.html