洛谷 P5319 / LOJ 3089 「BJOI2019」奥术神杖

洛谷传送门

LOJ 传送门

思路

设出现字符串的 \(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;
}
posted @ 2022-06-21 08:50  zltzlt  阅读(50)  评论(0编辑  收藏  举报