zoukankan      html  css  js  c++  java
  • 【BJOI 2019】奥术神杖

    题意

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

    $1le n,m,sle 1501,space 1le v_ile 10^9$

    题解

    王·能过就行·子健

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

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

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

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

    $$log_a{N}^k = k imes log_a{N}$$

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

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

    具体证明可以去翻书。

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

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

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

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

    $$log_a{ans} = frac{1}{c} imes log_a{(v_1 imes v_2 imes ... imes 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(10nslog{frac{ln v_{max}}{eps}})$。

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

      1 #include<bits/stdc++.h>
      2 #define N 1505
      3 #define inf 1e99
      4 #define eps 1e-6
      5 using namespace std;
      6 inline int read(){
      7     int x=0; bool f=1; char c=getchar();
      8     for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
      9     for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
     10     if(f) return x;
     11     return 0;
     12 }
     13 int n,m;
     14 char T[N];
     15 namespace AC{
     16     int cnt,ch[N][12],sum[N]; double val[N];
     17     inline void ins(char *s,double v){
     18         int u=0,len=strlen(s),c;
     19         for(int i=0;i<len;++i){
     20             c=s[i]-'0';
     21             if(!ch[u][c]) ch[u][c]=++cnt;
     22             u=ch[u][c];
     23         }
     24         ++sum[u], val[u]+=v;
     25     }
     26     int que[N],l,r,fail[N];
     27     void BuildAC(){
     28         fail[0]=-1, que[l=r=1]=0;
     29         while(l<=r){
     30             int u=que[l++];
     31             for(int i=0;i<10;++i)
     32                 if(!ch[u][i]) ch[u][i]=ch[fail[u]][i];
     33                 else fail[ch[u][i]]=ch[fail[u]][i], que[++r]=ch[u][i];
     34         }
     35         for(int i=2;i<=r;++i){
     36             sum[que[i]]+=sum[fail[que[i]]];
     37             val[que[i]]+=val[fail[que[i]]];
     38             //cout<<que[i]<<' '<<fail[que[i]]<<' '<<sum[que[i]]<<' '<<val[que[i]]<<endl;
     39         }
     40     }
     41     
     42     double f[N][N]; int g[N][N][2]; char ansStr[N];
     43     double DP(double x){
     44         //cout<<x<<endl;
     45         for(int j=0;j<=cnt;++j) val[j]-=sum[j]*x;
     46         for(int i=0;i<=n;++i)
     47             for(int j=0;j<=cnt;++j)
     48                 f[i][j]=-inf;
     49         f[0][0]=0;
     50         for(int i=0;i<n;++i){
     51             for(int j=0;j<=cnt;++j){
     52                 if(f[i][j]==-inf) continue;
     53                 if(T[i]=='.'){
     54                     for(int k=0;k<10;++k){
     55                         int _j=ch[j][k];
     56                         if(f[i+1][_j]<f[i][j]+val[_j]){
     57                             f[i+1][_j]=f[i][j]+val[_j];
     58                         }
     59                     }
     60                 }
     61                 else{
     62                     int k=T[i]-'0', _j=ch[j][k];
     63                     if(f[i+1][_j]<f[i][j]+val[_j]) f[i+1][_j]=f[i][j]+val[_j];
     64                 }
     65             }
     66         }
     67         for(int i=0;i<=cnt;++i) val[i]+=sum[i]*x;
     68         int ans=0;
     69         for(int j=1;j<=cnt;++j) if(f[n][j]>f[n][ans]) ans=j;
     70         //cout<<f[n][ans]<<endl;
     71         return f[n][ans];
     72     }
     73     void _DP(double x){
     74         for(int j=0;j<=cnt;++j) val[j]-=sum[j]*x;
     75         for(int i=0;i<=n;++i)
     76             for(int j=0;j<=cnt;++j)
     77                 f[i][j]=-inf;
     78         f[0][0]=0;
     79         for(int i=0;i<n;++i){
     80             for(int j=0;j<=cnt;++j){
     81                 if(f[i][j]==-inf) continue;
     82                 if(T[i]=='.'){
     83                     for(int k=0;k<10;++k){
     84                         int _j=ch[j][k];
     85                         if(f[i+1][_j]<f[i][j]+val[_j]){
     86                             f[i+1][_j]=f[i][j]+val[_j],
     87                             g[i+1][_j][0]=j, g[i+1][_j][1]=k;
     88                         }
     89                     }
     90                 }
     91                 else{
     92                     int k=T[i]-'0', _j=ch[j][k];
     93                     if(f[i+1][_j]<f[i][j]+val[_j])
     94                         f[i+1][_j]=f[i][j]+val[_j],
     95                         g[i+1][_j][0]=j, g[i+1][_j][1]=k;
     96                 }
     97             }
     98         }
     99         for(int i=0;i<=cnt;++i) val[i]+=sum[i]*x;
    100         int ans=0;
    101         for(int j=1;j<=cnt;++j) if(f[n][j]>f[n][ans]) ans=j;
    102         for(int i=n;i>0;--i){
    103             ansStr[i-1]=g[i][ans][1]+'0';
    104             ans=g[i][ans][0];
    105         }
    106     }
    107 }
    108 using namespace AC;
    109 int main(){
    110     //freopen("1.in","r",stdin);
    111     //freopen("1.out","w",stdout);
    112     n=read(), m=read(); scanf("%s",T);
    113     char S[N]; double V;
    114     for(int i=1;i<=m;++i){
    115         scanf("%s%lf",S,&V);
    116         ins(S,log(V));
    117     }
    118     BuildAC();
    119     double l=0, r=log(1e9+1), mid, ans=0;
    120     while(r-l>eps){
    121         mid=(l+r)/2;
    122         if(DP(mid)>0) ans=mid, l=mid;
    123         else r=mid;
    124     }
    125     //cout<<ans<<endl;
    126     _DP(ans);
    127     printf("%s
    ",ansStr);
    128     return 0;
    129 }
    Viev Code

    总结:

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

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

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

  • 相关阅读:
    指针,数组,字符串的区别(高质量程序设计指南C++/C语言第7章)
    bitset初始化问题
    书籍
    编译器的工作过程
    C++函数传递指向指针的指针的应用
    程序员面试金典--二叉树的下一个结点
    程序员面试金典--对称的二叉树
    程序员面试金典--按之字形顺序打印二叉树
    程序员面试金典--阶乘尾零
    程序员面试金典--矩阵元素查找
  • 原文地址:https://www.cnblogs.com/scx2015noip-as-php/p/bjoi2019d1t1.html
Copyright © 2011-2022 走看看