[BJOI2019] 奧術神杖 [取log+AC自動機+dp]

題面

傳送門ios

思路

首先,看到這個乘起來開根號的形式,應該能想到用取$\log$的方式作一個轉化:git

$\sqrt[n]{\prod_i a_i}=\frac{1}{n}\sum_i \log_b a_i$優化

這裏咱們把$b$取到$e$,就是$\ln a_i$了,不過實際上$b$取什麼都沒有問題ui

那麼,這個問題就轉化爲了求全部匹配的寶石序列的最大平均值spa

遇到這種多模式串、單模板串的狀況,應當第一時間想到AC自動機指針

咱們創建模式串的AC自動機,並在上面跑dp便可完成題目的求解code

創建AC自動機的時候,注意每一個節點須要繼承$fail$樹上全部祖先的信息!繼承

遇到這種有對選取的元素求平均值的最值的狀況,應當第一時間想到0-1分數規劃get

咱們二分最大平均值的答案,設當前爲$C$string

那麼如有一組匹配方式能達到這個$C$,或以上,則有:

$\frac{1}{siz}\sum_{i=1}^{siz}w_i\geq C$

$\sum_{i=1}^{siz}w_i\geq C\ast siz$

$\sum_{i=1}^{siz}(w_i-C)\geq 0$

因此咱們把每個取過$\log$的元素減去當前二分的$C$,在AC自動機上跑dp

這樣的好處是避免了須要在dp中維護已匹配元素個數的一個維度,能夠優化一個$n$的時間複雜度

創建AC自動機後,設$dp[i][u]$表示當前遍歷完成了模板串的前$i$個字符,匹配指針位置在AC自動機節點$u$上的狀況時的最大值。

若模板串的下一個字符是肯定的,就直接走到對應的兒子便可

不然須要更新每個$dp[i+1][son_u]$的值

詳細的更新方式見代碼

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cassert>
#include<queue>
#include<cmath>
#define ll long long
using namespace std;
inline int read(){
    int re=0,flag=1;char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(isdigit(ch)) re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
    return re*flag;
}
int n,m;double w[1510];
char a[1510],s[1510][1510];
int ch[1510][10],cntn=0,num[1510],fail[1510];
double sum[1510];
//AC Automaton
inline void insert(int x,int len){
    int i,cur=0,tmp;
    for(i=1;i<=len;i++){
        tmp=s[x][i]-'0';
        if(!ch[cur][tmp]) ch[cur][tmp]=++cntn;
        cur=ch[cur][tmp];
    }
    num[cur]++;sum[cur]+=w[x];
}
queue<int>q;
inline void build(){
    int i,u,v;
    for(i=0;i<10;i++){
        if(!ch[0][i]) continue;
        q.push(ch[0][i]);fail[ch[0][i]]=0;
    }
    while(!q.empty()){
        u=q.front();q.pop();
        num[u]+=num[fail[u]];
        sum[u]+=sum[fail[u]];
        for(i=0;i<10;i++){
            v=ch[u][i];
            if(v) fail[v]=ch[fail[u]][i],q.push(v);
            else ch[u][i]=ch[fail[u]][i];
        }
    }
}
//Dynamic Programming
double dp[1510][1510];
int from[1510][1510][2],endpos;
inline bool check(double mid){
    int i,j,k,son;
    for(i=0;i<=n;i++) for(j=0;j<=cntn;j++) dp[i][j]=-2e20;
    for(i=0;i<=cntn;i++){
        sum[i]-=mid*(double)num[i];//cut the value according to binary search process
    }
    dp[0][0]=0;
    for(i=1;i<=n;i++){
        for(j=0;j<=cntn;j++){
            if(dp[i-1][j]==-2e20) continue;
            if(a[i]!='.'){//character is fixed in original S
                son=ch[j][a[i]-'0'];
                if(dp[i][son]<dp[i-1][j]+sum[son]){
                    dp[i][son]=dp[i-1][j]+sum[son];
                    from[i][son][0]=j;//record the source of the maximum value
                    from[i][son][1]=a[i]-'0';//record the corresponding character
                }
            }
            else{//character is unfixed
                for(k=0;k<10;k++){
                    son=ch[j][k];
                    if(dp[i][son]<dp[i-1][j]+sum[son]){
                        dp[i][son]=dp[i-1][j]+sum[son];
                        from[i][son][0]=j;
                        from[i][son][1]=k;
                    }
                }
            }
        }
    }
    int pos=0;
    for(i=1;i<=cntn;i++) if(dp[n][i]>dp[n][pos]) pos=i;
    for(i=0;i<=cntn;i++) sum[i]+=mid*(double)num[i];//repair the value cut
    endpos=pos;return dp[n][pos]>0;//determine if largest value is over zero
}
char re[1510];
int main(){
    n=read();m=read();int i;
    scanf("%s",a+1);
    for(i=1;i<=m;i++){
        scanf("%s",s[i]+1);
        w[i]=read();
        w[i]=log((double)w[i]);
        insert(i,strlen(s[i]+1));
    }
    build();
    double l=0,r=log(1e9+7),mid,ans=0;
    while(r-l>1e-6){//binary search
        mid=(l+r)*0.5;
        if(check(mid)) ans=mid,l=mid;
        else r=mid;
    }
    check(ans);
    for(i=n;i>=1;i--){//get the answer string
        re[i]=from[i][endpos][1]+'0';
        endpos=from[i][endpos][0];
    }
    for(i=1;i<=n;i++) putchar(re[i]);
    putchar('\n');
}
相關文章
相關標籤/搜索