「UVA 11468」Substring

Description

给定 \(n\) 个字符 \(c_1, c_2, \cdots c_n\) 和各自的选择概率 \(p_1, p_2, \cdots, p_n\),随机选择 \(L\) 次得到的随机字符串 \(S\) (每次独立随机)。给出 \(K\) 个模板串 \(t_1, t_2, \cdots, t_K\),计算 \(S\) 不包含任何一个模板串的概率。

多组数据,组数为 \(T\)

Hint

  • \(1\le T \le 50\)
  • \(1\le K\le 20\)
  • \(1\le |t_i|\le 20\)
  • \(c_i \in [\texttt{a}, \texttt{z}] \cup [\texttt{A}, \texttt{Z}] \cup [\texttt{0}, \texttt{9}]\)
  • \(1\le L \le 100\)
  • \(\sum_{i = 1}^n p_i = 1\)
  • \(\forall i \in [1, K], t_i\)\(n\) 个给定字符组成。

Solution

AC 自动机 + 动态规划

首先肯定是对所有 模板串 建 AC自动机,在模式串结尾标记一下表示不能走。然后按之类问题的套路来:

\(f(i, x)\) 为字符串长度为 \(i\),走到结点 \(x\) 且不走到标记结点的概率。

那么自然是 \(f(i - 1, x)\) 转移到 \(f(i, \delta(x, c))\) 的方向。状态转移方程:

\[f(i, y) = \sum\limits_{\delta(x, c) = y} f(i - 1, x) \times \text{prob}(c) \]

其中 \(\text{prob}(c)\) 表示选择字符 \(c\) 的概率。显然如果 \(y\) 点北标记为结尾就要跳过。


像这种 dp 会有一个坑:当一个结点的 fail 对应的结点是被标记了的,那么这个结点也应该被标记。为什么呢?因为 fail 指向的是当前的最长后缀,而一个以一个模板串作为后缀的字符串自然也不合法

Code

#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>

using namespace std;
const int P_L = 25;
const int M_L = 105;
const int K = 25;
const int S = 62;

int k, n, l;
double prob[S];

inline int trans(char c) {
	if (isdigit(c)) return c - '0';
	else if (islower(c)) return c - 'a' + 10;
	else return c - 'A' + 36;
}

namespace ACAM {
	const int T = K * P_L;
	struct Node {
		int ch[S];
		int fail;
		bool statu;
	} t[T];
	double f[M_L][T];
	int total;
	
	void restart() {
		using namespace ACAM;
		memset(f, 0, sizeof f);
		memset(t, 0, sizeof t);
		total = 0;
	}
	
	void insert(char* s) {
		int x = 0;
		for (register int i = 0; s[i]; i++) {
			int c = trans(s[i]);
			if (!t[x].ch[c]) t[x].ch[c] = ++total;
			x = t[x].ch[c];
		}
		t[x].statu = true;
	}
	
	void build_fail() {
		queue<int> Q;
		for (register int i = 0; i < S; i++)
			if (t[0].ch[i]) Q.push(t[0].ch[i]), t[t[0].ch[i]].fail = 0;
		while (!Q.empty()) {
			int x = Q.front(); Q.pop();
			for (register int c = 0; c < S; c++)
				if (t[x].ch[c]) {
					Q.push(t[x].ch[c]);
					t[t[x].ch[c]].fail = t[t[x].fail].ch[c];
				} else t[x].ch[c] = t[t[x].fail].ch[c];
			t[x].statu |= t[t[x].fail].statu; // 此坑的解决方案在此体现
		}
	}
	
	double solve(int l) {
		f[0][0] = 1.0;
		for (register int i = 1; i <= l; i++)
			for (register int x = 0; x <= total; x++)
				for (register int c = 0; c < S; c++)
					if (!t[t[x].ch[c]].statu)
						f[i][t[x].ch[c]] += f[i - 1][x] * prob[c];
		double ret = 0.0;
		for (register int i = 0; i <= total; i++)
			ret += f[l][i];
		return ret;
	}
};

char str[M_L];
signed main() {	
	int total_tc;
	scanf("%d", &total_tc);
	
	for (int tc = 1; tc <= total_tc; tc++) {
		ACAM::restart();
		memset(prob, 0, sizeof prob);
		
		scanf("%d", &k);
		for (register int i = 1; i <= k; i++) {
			scanf("%s", str);
			ACAM::insert(str);
		}
		
		scanf("%d", &n);
		for (register int i = 1; i <= n; i++) {
			char lt[5]; double pb;
			scanf("%s%lf", lt, &pb);
			prob[trans(lt[0])] += pb;
		}
		
		ACAM::build_fail();
		scanf("%d", &l);
		double ans = ACAM::solve(l);
		
		printf("Case #%d: %.6f\n", tc, ans);
	}
	return 0;
}

猜你喜欢

转载自www.cnblogs.com/-Wallace-/p/12945723.html