傳送門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]$的值
詳細的更新方式見代碼
#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'); }