ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

「BJOI2019」奥术神杖

2021-01-17 18:02:23  阅读:210  来源: 互联网

标签:BJOI2019 奥术 log int sum tr kN 神杖 mid


知识点:01 分数规划,ACAM。

原题面:LojLuogu

没写 build 函数调一天哈哈

简述

给定一只由数字和\(\texttt{.}\)构成的字符串 \(s\)。给定 \(m\) 个特殊串 \(t_{1}\sim t_{m}\),\(t_i\) 的权值为 \(v_i\)。
需要在 \(s\) 中为\(\texttt{.}\)的位置上填入数字,一种填入方案的价值定义为:

\[\sqrt[c]{\prod_{i=1}^{c} w_i} \]

其中 \(w\) 表示在该填入方案中,出现过的特殊串的价值的可重集合,其大小为 \(c\)。

每个位置填入的数字任意,最大化填入方案的价值,并输出任意一个方案。
\(1\le m,|s|,\sum|t_i|\le 1501\),\(1\le v_i\le 10^9\)。
1S,512MB。

分析

对于两种填入方案,我们只关心它们价值的相对大小。带着根号不易比较大小,套路地取个对数,之后化下式子:

\[\begin{aligned} \large \log {\sqrt[c]{\prod_{i=1}^{c} w_i}} =& \dfrac{\log {\left(\prod\limits_{i=1}^{c} w_i\right)}}{c}\\ =& \dfrac{\sum\limits_{i=1}^{c} \log {w_i}}{c} \end{aligned}\]

这是一个显然的 01 分数规划的形态,考虑二分答案。存在一种填入方案价值不小于 \(mid\) 的充要条件为:

\[\begin{aligned} \dfrac{\sum\limits_{i=1}^{c} \log {w_i}}{c}\ge mid \iff \sum\limits_{i=1}^{c}\left(\log {w_i} - mid\right)\ge 0 \end{aligned}\]


考虑 DP 检查二分量 \(mid\) 是否合法。
具体地,先将特殊串 \(t_i\) 的权值设为 \(\log v_i - mid\),更新 ACAM 上各状态的权值,之后在 ACAM 上模拟匹配过程套路 DP。
设 \(f_{i,j}\) 表示长度为 \(i\),在 ACAM 上匹配的结束状态为 \(j\) 的串的最大价值。
初始化 \(f_{0,0} = 0\),转移时枚举串长,状态,转移函数。注意某一位不为\(\texttt{.}\)时转移函数只能为串中的字符,则有:

\[f_{i,j} = \begin{cases} &\max\limits_{\operatorname{trans}(u, s_i) = j} f_{i-1, u} + \operatorname{val}_{j} &(s_i\not= \texttt{.})\\ &\max\limits_{\operatorname{trans}(u, k) = j} f_{i-1, u} + \operatorname{val}_{j} &(s_i= \texttt{.}) \end{cases}\]

注意记录转移时的前驱与转移函数,根据前驱还原出方案即可。
总复杂度 \(O(\left(10|s|\cdot\sum |t_i|\right)\log w)\) 级别,\(\log w\) 为二分次数。

代码

//知识点:ACAM,分数规划
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
#define DB double 
const int kN = 3e3 + 10;
const DB kInf = 1e10;
const DB eps = 1e-6;
//=============================================================
int n, m;
char origin[kN], s[kN], ans[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
namespace ACAM {
  int node_num = 0, tr[kN][10], fail[kN], cnt[kN], from[kN][kN];
  DB sum[kN], val[kN], f[kN][kN];
  char ch[kN][kN];
  void Insert(char *s_, int val_) {
    int u_ = 0, lth = strlen(s_ + 1);
    for (int i = 1; i <= lth; ++ i) {
      if (! tr[u_][s_[i] - '0']) tr[u_][s_[i] - '0'] = ++ node_num;
      u_ = tr[u_][s_[i] - '0'];
    }
    sum[u_] += log(val_);
    cnt[u_] ++;
  }
  void Build() {
    std::queue <int> q;
    for (int i = 0; i < 10; ++ i) {
      if (tr[0][i]) q.push(tr[0][i]);
    }
    while (! q.empty()) {
      int u_ = q.front(); q.pop();
      for (int i = 0; i < 10; ++ i) {
        int v_ = tr[u_][i];
        if (v_) {
          fail[v_] = tr[fail[u_]][i];
          sum[v_] += sum[fail[v_]];
          cnt[v_] += cnt[fail[v_]];
          q.push(v_);
        } else {
          tr[u_][i] = tr[fail[u_]][i];
        }
      }
    }
  }
  bool DP(DB mid_) {
    //初始化
    for (int i = 0; i <= node_num; ++ i) val[i] = sum[i] - cnt[i] * mid_;
    for (int i = 0; i <= n; ++ i) {
      for (int j = 0; j <= node_num; ++ j) {
        f[i][j] = -kInf;
      }
    }
    f[0][0] = 0;

    //DP
    for (int i = 0; i < n; ++ i) {
      for (int j = 0; j <= node_num; ++ j) {
        if (f[i][j] == -kInf) continue;
        if (origin[i + 1] == '.') {
          for (int k = 0; k < 10; ++ k) {
            int v_ = tr[j][k];
            if (f[i + 1][v_] < f[i][j] + val[v_]) {
              f[i + 1][v_] = f[i][j] + val[v_];
              from[i + 1][v_] = j;
              ch[i + 1][v_] = k + '0';
            }
          }
        } else {
          int v_ = tr[j][origin[i + 1] - '0'];
          if (f[i + 1][v_] < f[i][j] + val[v_]) {
            f[i + 1][v_] = f[i][j] + val[v_];
            from[i + 1][v_] = j;
            ch[i + 1][v_] = origin[i + 1];
          }
        }
      }
    }

    //寻找最优解
    int pos = 0;
    for (int i = 0; i <= node_num; ++ i) {
      if (f[n][i] > f[n][pos]) pos = i;
    }
    if (f[n][pos] <= 0) return false;
    for (int i = n, j = pos; i; -- i) {
      ans[i] = ch[i][j];
      j = from[i][j];
    }
    return true;
  }
}
//=============================================================
int main() {
  n = read(), m = read();
  scanf("%s", origin + 1);
  for (int i = 1; i <= m; ++ i) {
    scanf("%s", s + 1);
    int val = read();
    ACAM::Insert(s, val);
  }
  ACAM::Build();
  for (DB l = 0, r = log(kInf); r - l >= eps; ) {
    DB mid = (l + r) / 2.0;
    if (ACAM::DP(mid)) {
      l = mid;
    } else {
      r = mid;
    }
  }
  printf("%s", ans + 1);
  return 0; 
}

标签:BJOI2019,奥术,log,int,sum,tr,kN,神杖,mid
来源: https://www.cnblogs.com/luckyblock/p/14289656.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有