【BJOI 2019】奥术神杖
题意
你有一个长度为 $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】奥术神杖的更多相关文章
- Loj #3089. 「BJOI2019」奥术神杖
Loj #3089. 「BJOI2019」奥术神杖 题目描述 Bezorath 大陆抵抗地灾军团入侵的战争进入了僵持的阶段,世世代代生活在 Bezorath 这片大陆的精灵们开始寻找远古时代诸神遗留的 ...
- [BJOI2019]奥术神杖(分数规划,动态规划,AC自动机)
[BJOI2019]奥术神杖(分数规划,动态规划,AC自动机) 题面 洛谷 题解 首先乘法取\(log\)变加法,开\(c\)次根变成除\(c\). 于是问题等价于最大化\(\displaystyle ...
- [BJOI2019]奥术神杖——AC自动机+DP+分数规划+二分答案
题目链接: [BJOI2019]奥术神杖 答案是$ans=\sqrt[c]{\prod_{i=1}^{c}v_{i}}=(\prod_{i=1}^{c}v_{i})^{\frac{1}{c}}$. 这 ...
- 【LOJ】#3089. 「BJOI2019」奥术神杖
LOJ#3089. 「BJOI2019」奥术神杖 看见乘积就取log,开根号就是除法,很容易发现这就是一道01分数规划.. 然后建出AC自动机直接dp就行,判断条件要设成>0,因为起点的值是1, ...
- luoguP5319 [BJOI2019]奥术神杖(分数规划,AC自动机DP)
luoguP5319 [BJOI2019]奥术神杖(分数规划,AC自动机DP) Luogu 题解时间 难点在于式子转化,设有c个满足的子串,即求最大的 $ ans = \sqrt[c]{\prod_{ ...
- 【题解】Luogu P5319 [BJOI2019]奥术神杖
原题传送门 题目让我们最大化\(val=\sqrt[k]{\prod_{i=1}^k w_i}\),其中\(k\)是咒语的个数,\(w_i\)是第\(i\)个咒语的神力 看着根号和累乘不爽,我们两边同 ...
- [BJOI2019]奥术神杖
https://www.luogu.org/problemnew/show/P5319 题解 首先观察我们要求的答案的形式: \[ \biggl(\prod V_i \biggr)^x\ \ \ x= ...
- [BJOI2019]奥术神杖(分数规划+AC自动机+DP)
题解:很显然可以对权值取对数,然后把几何平均值转为算术平均值,然后很显然是分数规划.先对每个模式串建立AC自动机,每个节点w[i],sz[i]分别表示以其为前缀的字符串,然后再二分最优解k,然后w[i ...
- luogu P5319 [BJOI2019]奥术神杖
传送门 要求的东西带个根号,这玩意叫几何平均数,说到平均数,我们就能想到算术平均数(就是一般意义下的平均数),而这个东西是一堆数之积开根号,所以如果每个数取对数,那么乘法会变成加法,开根号变成除法,所 ...
随机推荐
- 初尝微信小程序3-移动设备的分辨率与rpx
屏幕尺寸就是实际的物理尺寸. 分辨率(pt),是逻辑分辨率,pt的大小只和屏幕尺寸有关,简单可以理解为长度和视觉单位. 分辨率(px),是物理分辨率,单位是像素点,和屏幕尺寸没有关系. 微信开发者工具 ...
- hello spring boot neo4j
新建springboot 项目: https://www.cnblogs.com/lcplcpjava/p/7406253.html bug fixs: 1. Maven Configuration ...
- SummerVocation_Leaning--java动态绑定(多态)
概念: 动态绑定:在执行期间(非编译期间)判断所引用的对象的实际类型,根据实际类型调用其相应的方法.如下例程序中,根据person对象的成员变量pet所引用的不同的实际类型调用相应的方法. 具体实现好 ...
- Python基础2-Python中文乱码(转)
转自:https://blog.csdn.net/apache0554/article/details/53889253 前言:中文编码问题一直是程序员头疼的问题,而Python2中的字符编码足矣令新 ...
- 深入理解java虚拟机读书笔记1--java内存区域
Java在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途.创建和销毁的时间,有一些是随虚拟机的启动而创建,随虚拟机的退出而销毁,有些则是与线程一一对应,随 ...
- 100个linux系统常用指令
1.ls [选项] [目录名 | 列出相关目录下的所有目录和文件 -a 列出包括.a开头的隐藏文件的所有文件-A 通-a,但不列出"."和".."-l 列出文件 ...
- 九、Linux 磁盘管理
Linux 磁盘管理 Linux磁盘管理好坏直接关系到整个系统的性能问题. Linux磁盘管理常用三个命令为df.du和fdisk. df:列出文件系统的整体磁盘使用量 du:检查磁盘空间使用量 fd ...
- spark实战之网站日志分析
前面一篇应该算是比较详细的介绍了spark的基础知识,在了解了一些spark的知识之后相必大家对spark应该不算陌生了吧!如果你之前写过MapReduce,现在对spark也很熟悉的话我想你再也不想 ...
- python 爬虫豆瓣top250
网页api:https://movie.douban.com/top250?start=0&filter= 用到的模块:urllib,re,csv 捣鼓一上午终于好了,有些小问题 (top21 ...
- 问题 1936: [蓝桥杯][算法提高VIP]最大乘积
问题 1936: [蓝桥杯][算法提高VIP]最大乘积 时间限制: 1Sec 内存限制: 128MB 提交: 77 解决: 16 题目描述 对于n个数,从中取出m个数,如何取使得这m个数的乘积最大呢? ...