「BJOI2019」奥术神杖

知识点: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」奥术神杖(AC自动机+DP)


下一篇:利用colinux制作tinycolinx,在ecs上打造server farm和vps iaas环境代替docker