题意

你有一个长度为 $n$ 的模板串(由 $0-9$ 这 $10$ 个数字和通配符 $.$ 组成),还有 $m$ 个匹配串(只由 $0-9$ 这 $10$ 个数字组成),每个匹配串有一个魔力值 $v_i$。你要把模板串的每个 $.$ 都换成一个数字,使得模板串的魔力值最大。模板串的魔力值定义为:模板串中每出现一次任意一个匹配串 $s_i$,字符串的魔力就 $\times v_i$。最终魔力值开 $c$ 次方根,$c$ 为模板串中出现的匹配串的总数。

$1\le n,m,s\le 1501,\space 1\le v_i\le 10^9$

题解

王·能过就行·子健

显然只要三个 $10^9$ 大小的数乘起来就爆 $long\space long$ 了(即 $\prod v_i$ 会很大),而高精度开根既难写又爆复杂度(光乘法就爆时间了),所以不能直接按题目的公式求。

如果你没学过数学(比如我),可以把所有 $v_i$ 各自开 $c$ 次方根再相乘,但即使开 $long\space double$ 也会爆精度,不过可以拿 $80$ 分。

如果你学过数学,应该记得高一数学必修 $1$ 中有一章讲了关于 $log$ 的各种性质,其中有两条是

$$\log_a{MN} = \log_a{M}+\log_a{N}$$

$$\log_a{N}^k = k\times \log_a{N}$$

其中 $a$ 可以是任意实底数。

第一条式子中的 $MN$ 可以拓展成任意多个乘数,等号右边就会得到一堆 $log$ 值相加。简单地说就是因为幂值相乘等于指数相加(比如 $2^4$ 变成 $2^5$ 次方,值乘了 $2$,但指数只加了 $1$)。

具体证明可以去翻书。

把两个公式组合一下,就可以推这题的公式

$$ans = \sqrt[c]{v_1\times v_2\times ...\times v_k} = (v_1\times v_2\times ...\times v_k)^{\frac{1}{c}}$$

两边同时取以一个实数 $a$ 为底的对数,得到

$$\log_a{ans} = \log_a{(v_1\times v_2\times ...\times v_k)^{\frac{1}{c}}}$$

$$\log_a{ans} = \frac{1}{c}\times \log_a{(v_1\times v_2\times ...\times v_k)}$$

$$\log_a{ans} = \frac{1}{c}(\log_a{v_1}+\log_a{v_2}+...+\log_a{v_k})$$

因为这题只需要你求方案,所以你只要确保不同方案之间的相对魔力值即可,不用维护具体的 $ans$ 值,所以可以把 $ans$ 取 $log$,$log$ 的底数 $a$ 也可以随便取,大部分人应该都取的是自然对数 $e$

不难发现等号右边变成了一个类似于平均数的东西,仔细观察即可发现,把所有匹配串的魔力值 $v_i$ 取 $ln$ 后,你要使出现的所有匹配串的 $v_i$ 的平均数最大。

平均数最大这种东西就是套路的01分数规划……

具体做法就是,二分平均数 $x$,然后把所有匹配串的 $a_i$ 都减去 $x$,问题就变成了如何使 $v_i$ 之和最大。在所有模板串组成的 AC 自动机上 $dp$ 即可。

AC 自动机上 $dp$ 的状态就是 $f_{i,j}$ 表示确定模板串的前 $i$ 位,按模板串的前 $i$ 位跑 AC 自动机到达的点的编号为 $j$ 时,模板串的魔力值最大是多少。

然后判断一下模板串的第 $i$ 位是不是通配符就行了,是的话就可以往任意儿子转移,不是的话就要沿对应的字符边转移。

时间复杂度 $O(10ns\log{\frac{\ln v_{max}}{eps}})$。

这他吗什么复杂度,怎么跑过的……能过就行了

 #include<bits/stdc++.h>
#define N 1505
#define inf 1e99
#define eps 1e-6
using namespace std;
inline int read(){
int x=; bool f=; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=;
for(; isdigit(c);c=getchar()) x=(x<<)+(x<<)+(c^'');
if(f) return x;
return ;
}
int n,m;
char T[N];
namespace AC{
int cnt,ch[N][],sum[N]; double val[N];
inline void ins(char *s,double v){
int u=,len=strlen(s),c;
for(int i=;i<len;++i){
c=s[i]-'';
if(!ch[u][c]) ch[u][c]=++cnt;
u=ch[u][c];
}
++sum[u], val[u]+=v;
}
int que[N],l,r,fail[N];
void BuildAC(){
fail[]=-, que[l=r=]=;
while(l<=r){
int u=que[l++];
for(int i=;i<;++i)
if(!ch[u][i]) ch[u][i]=ch[fail[u]][i];
else fail[ch[u][i]]=ch[fail[u]][i], que[++r]=ch[u][i];
}
for(int i=;i<=r;++i){
sum[que[i]]+=sum[fail[que[i]]];
val[que[i]]+=val[fail[que[i]]];
//cout<<que[i]<<' '<<fail[que[i]]<<' '<<sum[que[i]]<<' '<<val[que[i]]<<endl;
}
} double f[N][N]; int g[N][N][]; char ansStr[N];
double DP(double x){
//cout<<x<<endl;
for(int j=;j<=cnt;++j) val[j]-=sum[j]*x;
for(int i=;i<=n;++i)
for(int j=;j<=cnt;++j)
f[i][j]=-inf;
f[][]=;
for(int i=;i<n;++i){
for(int j=;j<=cnt;++j){
if(f[i][j]==-inf) continue;
if(T[i]=='.'){
for(int k=;k<;++k){
int _j=ch[j][k];
if(f[i+][_j]<f[i][j]+val[_j]){
f[i+][_j]=f[i][j]+val[_j];
}
}
}
else{
int k=T[i]-'', _j=ch[j][k];
if(f[i+][_j]<f[i][j]+val[_j]) f[i+][_j]=f[i][j]+val[_j];
}
}
}
for(int i=;i<=cnt;++i) val[i]+=sum[i]*x;
int ans=;
for(int j=;j<=cnt;++j) if(f[n][j]>f[n][ans]) ans=j;
//cout<<f[n][ans]<<endl;
return f[n][ans];
}
void _DP(double x){
for(int j=;j<=cnt;++j) val[j]-=sum[j]*x;
for(int i=;i<=n;++i)
for(int j=;j<=cnt;++j)
f[i][j]=-inf;
f[][]=;
for(int i=;i<n;++i){
for(int j=;j<=cnt;++j){
if(f[i][j]==-inf) continue;
if(T[i]=='.'){
for(int k=;k<;++k){
int _j=ch[j][k];
if(f[i+][_j]<f[i][j]+val[_j]){
f[i+][_j]=f[i][j]+val[_j],
g[i+][_j][]=j, g[i+][_j][]=k;
}
}
}
else{
int k=T[i]-'', _j=ch[j][k];
if(f[i+][_j]<f[i][j]+val[_j])
f[i+][_j]=f[i][j]+val[_j],
g[i+][_j][]=j, g[i+][_j][]=k;
}
}
}
for(int i=;i<=cnt;++i) val[i]+=sum[i]*x;
int ans=;
for(int j=;j<=cnt;++j) if(f[n][j]>f[n][ans]) ans=j;
for(int i=n;i>;--i){
ansStr[i-]=g[i][ans][]+'';
ans=g[i][ans][];
}
}
}
using namespace AC;
int main(){
//freopen("1.in","r",stdin);
//freopen("1.out","w",stdout);
n=read(), m=read(); scanf("%s",T);
char S[N]; double V;
for(int i=;i<=m;++i){
scanf("%s%lf",S,&V);
ins(S,log(V));
}
BuildAC();
double l=, r=log(1e9+), mid, ans=;
while(r-l>eps){
mid=(l+r)/;
if(DP(mid)>) ans=mid, l=mid;
else r=mid;
}
//cout<<ans<<endl;
_DP(ans);
printf("%s\n",ansStr);
return ;
}

Viev Code

总结:

1. 这类题不能直接 $dp$ 求最大平均数。因为求最大平均数这种问题,除了分数规划外(即二分答案),只能在某些情况下用贪心(比如从大到小取)。

若不能贪心,我们不能把上述 $dp$ 的值直接记为最大平均数 或者同时记一个最小的匹配数量。考虑平均数这个东西的本质,对于到达同一状态的两种情况,可能一种情况匹配的数少,平均数也更小;但把两种情况同时加入一个新数,这种情况的新平均数就可能比另一种情况的新平均数大了。比如两个数集 $\{10\}$ 和 $\{9,9,9,14\}$,前者的平均数是 $10$,后者的平均数是 $10.25$;但把两个数集同时加入一个数 $11$,前者的平均数变成了 $10.5$,后者的平均数变成了 $10.4$。所以如果用 $dp$ 求最大平均数,必须再开一维状态记匹配的串数(即要求多少个数的平均数),但匹配的串数可能很多,再开一维状态的话时空复杂度都不能承受。所以只能分数规划。

2. 这道题告诉我们一定要学好数学这门文化课,否则会被数学杀。

【BJOI 2019】奥术神杖的更多相关文章

  1. Loj #3089. 「BJOI2019」奥术神杖

    Loj #3089. 「BJOI2019」奥术神杖 题目描述 Bezorath 大陆抵抗地灾军团入侵的战争进入了僵持的阶段,世世代代生活在 Bezorath 这片大陆的精灵们开始寻找远古时代诸神遗留的 ...

  2. [BJOI2019]奥术神杖(分数规划,动态规划,AC自动机)

    [BJOI2019]奥术神杖(分数规划,动态规划,AC自动机) 题面 洛谷 题解 首先乘法取\(log\)变加法,开\(c\)次根变成除\(c\). 于是问题等价于最大化\(\displaystyle ...

  3. [BJOI2019]奥术神杖——AC自动机+DP+分数规划+二分答案

    题目链接: [BJOI2019]奥术神杖 答案是$ans=\sqrt[c]{\prod_{i=1}^{c}v_{i}}=(\prod_{i=1}^{c}v_{i})^{\frac{1}{c}}$. 这 ...

  4. 【LOJ】#3089. 「BJOI2019」奥术神杖

    LOJ#3089. 「BJOI2019」奥术神杖 看见乘积就取log,开根号就是除法,很容易发现这就是一道01分数规划.. 然后建出AC自动机直接dp就行,判断条件要设成>0,因为起点的值是1, ...

  5. luoguP5319 [BJOI2019]奥术神杖(分数规划,AC自动机DP)

    luoguP5319 [BJOI2019]奥术神杖(分数规划,AC自动机DP) Luogu 题解时间 难点在于式子转化,设有c个满足的子串,即求最大的 $ ans = \sqrt[c]{\prod_{ ...

  6. 【题解】Luogu P5319 [BJOI2019]奥术神杖

    原题传送门 题目让我们最大化\(val=\sqrt[k]{\prod_{i=1}^k w_i}\),其中\(k\)是咒语的个数,\(w_i\)是第\(i\)个咒语的神力 看着根号和累乘不爽,我们两边同 ...

  7. [BJOI2019]奥术神杖

    https://www.luogu.org/problemnew/show/P5319 题解 首先观察我们要求的答案的形式: \[ \biggl(\prod V_i \biggr)^x\ \ \ x= ...

  8. [BJOI2019]奥术神杖(分数规划+AC自动机+DP)

    题解:很显然可以对权值取对数,然后把几何平均值转为算术平均值,然后很显然是分数规划.先对每个模式串建立AC自动机,每个节点w[i],sz[i]分别表示以其为前缀的字符串,然后再二分最优解k,然后w[i ...

  9. luogu P5319 [BJOI2019]奥术神杖

    传送门 要求的东西带个根号,这玩意叫几何平均数,说到平均数,我们就能想到算术平均数(就是一般意义下的平均数),而这个东西是一堆数之积开根号,所以如果每个数取对数,那么乘法会变成加法,开根号变成除法,所 ...

随机推荐

  1. CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第二节

    原文链接 第二节:第一个内核 Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多个国家级的实验室进行大型并 ...

  2. Activiti学习记录(五)

    1.排他网关 说明: 1) 一个排他网关对应一个以上的顺序流 2) 由排他网关流出的顺序流都有个conditionExpression元素,在内部维护返回boolean类型的决策结果. 3) 决策网关 ...

  3. 委托、事件与Observer设计模式

    范例说明 上面的例子已不足以再进行下面的讲解了,我们来看一个新的范例,因为之前已经介绍了很多的内容,所以本节的进度会稍微快一些: 假设我们有个高档的热水器,我们给它通上电,当水温超过95度的时候:1. ...

  4. swift 循环语句

    // // main.swift // switch // // Created by lanou on 16/10/21. // Copyright (c) 2016年 lanou. All rig ...

  5. vue框架初学习的基本指令

           学习地址:<ahref="https: cn.vuejs.="" org="" "="" targe ...

  6. SpringBoot之YAML

    SpringBoot的配置文件有两种,一种是properties结尾的,一种是以yaml或yml文件结尾的 我们讨论一下yml文件结尾的文件: 基本语法: 其实yml文件就是键值对的形式,不过就是键( ...

  7. windows 时间同步至最新时间方法 | windows 时间同步服务器

    国内 windows 系统的电脑有时候不能自动同步互联网当前时间,这就需要改一下 windows 的时间同步服务器 版权声明:本文为博主原创文章,未经博主允许不得转载. 原文地址:https://ww ...

  8. hprose 1.0(rpc 框架) - 关于跨域和P3P的声明

    private function sendHeader($context) { if ($this->onSendHeader !== null) { $sendHeader = $this-& ...

  9. JZOJ 5184. 【NOIP2017提高组模拟6.29】Gift

    5184. [NOIP2017提高组模拟6.29]Gift (Standard IO) Time Limits: 1000 ms  Memory Limits: 262144 KB  Detailed ...

  10. Cube HDU - 1220(思维)

    Cowl is good at solving math problems. One day a friend asked him such a question: You are given a c ...