洛谷 P5319 / LOJ 3089 「BJOI2019」奥术神杖
思路
设出现字符串的 \(V_i\) 可重集为 \(S = \{w_i\},\)令 \(ans = \sqrt[c]{\prod\limits_{i=1}^c w_i}\)。
直接求 \(ans\) 的最大值不好处理,考虑两边取对数。
\[\ln ans = \dfrac{\sum\limits_{i=1}^c \ln w_i}{c}
\]
发现上述式子很像 0/1 分数规划的问题形式。考虑二分 \(\ln ans\),若 \(mid\) 可行,则有:
\[\dfrac{\sum\limits_{i=1}^c \ln w_i}{c} > mid
\]
\[\sum\limits_{i=1}^c \ln w_i > mid \cdot c
\]
\[(\sum\limits_{i=1}^c \ln w_i) - mid \cdot c > 0
\]
\[\sum\limits_{i=1}^c (\ln w_i - mid) > 0
\]
先对 \(V_i\) 取 \(\ln\),接着对 \(m\) 个数字串建 AC 自动机,每个结点维护两个值 \(cnt_i\) 和 \(sum_i\),分别表示以结点 \(i\) 结尾的串的个数和串的 \(\ln V_i\) 之和。
每次二分时先将 \(sum_i \gets cnt_i \cdot mid\),然后 dp。设 \(f_{i,u}\) 为当前填到第 \(i\) 个数字,当前在 AC 自动机的编号为 \(u\) 的结点上,\(\ln w_i - mid\) 的最大值。转移时枚举下一个位置填的数字即可。注意要记录方案,方便输出。
代码
code
/*
p_b_p_b txdy
AThousandMoon txdy
AThousandSuns txdy
hxy txdy
*/
#include <bits/stdc++.h>
#define pb push_back
#define fst first
#define scd second
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1520;
int n, m, ans[maxn];
double a[maxn], f[maxn][maxn];
char s[maxn], t[maxn];
pii pre[maxn][maxn];
struct AC {
int ch[maxn][10], fail[maxn], tot, cnt[maxn];
double sum[maxn];
void insert(char *s, double x) {
int p = 0;
for (int i = 0; s[i]; ++i) {
if (!ch[p][s[i] - '0']) {
ch[p][s[i] - '0'] = ++tot;
}
p = ch[p][s[i] - '0'];
}
++cnt[p];
sum[p] += x;
}
void build() {
queue<int> q;
for (int i = 0; i < 10; ++i) {
if (ch[0][i]) {
q.push(ch[0][i]);
}
}
while (q.size()) {
int u = q.front();
q.pop();
cnt[u] += cnt[fail[u]];
sum[u] += sum[fail[u]];
for (int i = 0; i < 10; ++i) {
if (ch[u][i]) {
fail[ch[u][i]] = ch[fail[u]][i];
q.push(ch[u][i]);
} else {
ch[u][i] = ch[fail[u]][i];
}
}
}
}
bool check(double x, bool flag) {
for (int i = 0; i <= tot; ++i) {
sum[i] -= x * cnt[i];
}
for (int i = 0; i <= n; ++i) {
for (int j = 0; j <= tot; ++j) {
f[i][j] = -1e18;
}
}
f[0][0] = 0;
for (int i = 1; i <= n; ++i) {
for (int u = 0; u <= tot; ++u) {
if (f[i - 1][u] < -1e17) {
continue;
}
if (t[i] == '.') {
for (int j = 0; j < 10; ++j) {
int v = ch[u][j];
if (f[i - 1][u] + sum[v] > f[i][v]) {
f[i][v] = f[i - 1][u] + sum[v];
pre[i][v] = make_pair(u, j);
}
}
} else {
int v = ch[u][t[i] - '0'];
if (f[i - 1][u] + sum[v] > f[i][v]) {
f[i][v] = f[i - 1][u] + sum[v];
pre[i][v] = make_pair(u, t[i] - '0');
}
}
}
}
double res = -1e18;
int u = -1;
for (int i = 0; i <= tot; ++i) {
if (f[n][i] > res) {
res = f[n][i];
u = i;
}
}
if (flag) {
for (int i = n; i; --i) {
ans[i] = pre[i][u].scd;
u = pre[i][u].fst;
}
}
for (int i = 0; i <= tot; ++i) {
sum[i] += x * cnt[i];
}
return res > 0;
}
} ac;
void solve() {
scanf("%d%d%s", &n, &m, t + 1);
for (int i = 1; i <= m; ++i) {
double x;
scanf("%s%lf", s, &x);
a[i] = log(x);
ac.insert(s, a[i]);
}
ac.build();
double l = 0, r = log(2e9);
while (r - l > 1e-6) {
double mid = (l + r) / 2;
if (ac.check(mid, 0)) {
l = mid;
} else {
r = mid;
}
}
ac.check(l, 1);
for (int i = 1; i <= n; ++i) {
printf("%d", ans[i]);
}
}
int main() {
int T = 1;
// scanf("%d", &T);
while (T--) {
solve();
}
return 0;
}