problem
神杖上从左到右镶嵌了 n n n 颗奥术宝石,奥术宝石一共有 10 10 10 种,用数字 “ 0123456789 ” “0123456789” “0123456789” 表示。有些位置的宝石已经残缺,用 “ . ” “.” “.” 表示,你需要用完好的奥术宝石填补每一处残缺的部分(每种奥术宝石个数不限,且不能够更换未残缺的宝石)。古老的魔法书上记载了 m m m 种咒语 ( S i , V i ) (S_i,V_i) (Si,Vi),其中 S i S_i Si 是一个非空数字串, V i V_i Vi 是这种组合能够激发的神力。
神杖的初始神力值 Magic=1 \texttt{Magic=1} Magic=1,每当神杖中出现了连续一段宝石与 S i S_i Si 相等时,神力值 Magic \texttt{Magic} Magic 就会乘以 V i V_i Vi。但神杖如果包含了太多咒语就不再纯净导致神力降低:设 c c c 为神杖包含的咒语个数(若咒语类别相同但出现位置不同视为多次),神杖最终的神力值为 Magic c \sqrt[c]{\texttt{Magic}} cMagic 。(若 c = 0 c=0 c=0 则神杖最终神力值为 1 1 1。)
例如有两种咒语 ( 01 , 3 ) (01,3) (01,3)、 ( 10 , 4 ) (10,4) (10,4),那么神杖 “ 0101 ” “0101” “0101” 的神力值为 3 × 4 × 3 3 \sqrt[3]{3×4×3} 33×4×3。
你需要使修复好的神杖的最终的神力值最大,输出任何一个解即可。
数据范围: 1 ≤ m ≤ 1501 1≤m≤1501 1≤m≤1501, 1 ≤ V i ≤ 1 0 9 1≤V_i≤10^9 1≤Vi≤109。
solution
由于这是多个串的匹配问题,AC 自动机是少不了的,先把它建出来再说。
现在分析题目,假设串中有 c c c 个咒语,每个咒语的权值分别为 w i w_i wi,那么魔法值就是 Magic = ∏ i = 1 c w i c \texttt{Magic}=\sqrt[c]{\prod_{i=1}^cw_i} Magic=c∏i=1cwi。
我们两边取对数,把根号消掉,得到 ln Magic = 1 c ∑ i = 1 c ln w i \ln\texttt{Magic}=\frac 1 c\sum_{i=1}^c\ln w_i lnMagic=c1∑i=1clnwi。
由于我们只用比较 Magic \texttt{Magic} Magic 的相对大小,并不需要知道 Magic \texttt{Magic} Magic 具体的值,可以在一开始就对 w i w_i wi 取对数,最大化 1 c ∑ i = 1 c ln w i \frac 1 c\sum_{i=1}^c \ln w_i c1∑i=1clnwi。
比较显然的是可以 0 / 1 0/1 0/1 分数规划,设当前二分到的答案是 λ \lambda λ,则:
1 c ∑ i = 1 c ln w i > λ ∑ i = 1 c ( ln w i − λ ) > 0 \begin{aligned}\frac 1 c\sum_{i=1}^c\ln w_i&>\lambda\\\sum_{i=1}^c(\ln w_i-\lambda)&>0\end{aligned} c1i=1∑clnwii=1∑c(lnwi−λ)>λ>0
也就是说,我们把小串的权值改成 ln w i − λ \ln w_i-\lambda lnwi−λ,然后在自动机上 d p dp dp,看是否存在一种权值 > 0 >0 >0 的走法即可。
具体来说就是设 f [ i ] [ j ] f[i][j] f[i][j] 表示已经遍历了神杖上前 i i i 个字符,且当前在自动机的节点 j j j 的最大权值。那么只需枚举下一步怎么走然后转移即可(如果 i + 1 i+1 i+1 已经固定了那就只有 1 1 1 种走法)。
PS:代码实现稍微有点不一样,但是大体思路就是这样。
code
#include<bits/stdc++.h>
#define N 2005
#define eps 1e-6
using namespace std;
typedef pair<int,int> pii;
int n,m,tot,c[N][N],edge[N][N];
double f[N][N],v;
struct Trie{
double val;
int num,fail,son[26];
}T[N];
char S[N],a[N];
void Insert(){
int p=0,l=strlen(a+1);
for(int i=1;i<=l;++i){
int x=a[i]-'0';
if(!T[p].son[x]) T[p].son[x]=++tot;
p=T[p].son[x];
}
++T[p].num,T[p].val=v;
}
queue<int>Q;
void Get_fail(){
for(int i=0;i<10;++i)
if(T[0].son[i]) Q.push(T[0].son[i]);
while(!Q.empty()){
int x=Q.front();Q.pop();
T[x].num+=T[T[x].fail].num;
T[x].val+=T[T[x].fail].val;
for(int i=0;i<10;++i){
if(T[x].son[i]){
Q.push(T[x].son[i]);
T[T[x].son[i]].fail=T[T[x].fail].son[i];
}
else T[x].son[i]=T[T[x].fail].son[i];
}
}
}
bool check(double mid){
memset(f,-0x3f,sizeof(f));
f[0][0]=0;
for(int i=1;i<=n;++i){
for(int j=0;j<=tot;++j){
if(S[i]=='.'){
for(int k=0;k<10;++k){
int to=T[j].son[k];
double tmp=f[i-1][j]+T[to].val-mid*T[to].num;
if(tmp>f[i][to]) f[i][to]=tmp,c[i][to]=k,edge[i][to]=j;
}
}
else{
int to=T[j].son[S[i]-'0'];
double tmp=f[i-1][j]+T[to].val-mid*T[to].num;
if(tmp>f[i][to]) f[i][to]=tmp,edge[i][to]=j;
}
}
}
for(int i=0;i<=tot;++i) if(f[n][i]>eps) return true;
return false;
}
void Get(int x,int to){
if(!x) return;
if(S[x]=='.') S[x]=c[x][to]+'0';
Get(x-1,edge[x][to]);
}
int main(){
scanf("%d%d%s",&n,&m,S+1);
for(int i=1;i<=m;++i){
scanf("%s%lf",a+1,&v);
v=log(v),Insert();
}
Get_fail();
double l=0,r=100;
while(r-l>eps){
double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
check(l);
for(int i=0;i<=tot;++i) if(f[n][i]>eps) {Get(n,i);break;}
printf("%s\n",S+1);
return 0;
}