http://acm.whu.edu.cn/land/problem/detail?problem_id=1572
题意:给定n(<=8)个单词和一个长度L(<=14)
问随机写一个L长度的字符串(a..z等概率出现),出现所给单词个数的期望。
两种思路:
------------KMP------------
分别求每一个单词出现的概率相加即可。
求一个单词出现的概率不好求,可以转化为求其不出现的概率
对于某给定单词s,
用dp[i][j][k] 表示长度为i的随机字符串最后一个字符为k,最后k个字符与s[1..k]相等的概率
转移的时候控制k<|s|就可以保证s在随机字符串中不出现。
转移的时候要用到kmp,因为随机字符串加上的字符后可能导致和s[k+1]不匹配
这时候要用kmp来跳到一个合适的匹配位置。
例如,s = "abaa",
有状态dp[i][3]['a'], 表示长度为i的随机字符串后3个字符为"aba"
现在在后面加一个字符'b', 得到四个字符"abab", 和s匹配的长度变成了2
所以要转移到dp[i+1][2]['b']
#include <cstdio>
#include <cstring>
using namespace std;
const double PP = 1.0/26.0;
const double eps = 1e-8;
int n, L;
int next[20], a[20];
double f[20][30][30];
char s[20];
double solve(char s[]){
int n = strlen(s+1);
for (int i=1; i<=n; i++) a[i] = s[i]-'a';
next[1] = 0;
for (int i=2; i<=n; i++){
int j = next[i-1];
while (j && a[j+1] != a[i]) j = next[j];
if (a[j+1] == a[i]) j++;
next[i] = j;
}
memset(f, 0, sizeof(f));
for (int c=0; c<26; c++)
f[1][c][c==a[1]] = PP;
for (int i=1; i<L; i++)
for (int j=0; j<26; j++)
for (int k=0; k<n&&k<=i; k++) if (f[i][j][k]>eps){
for (int c=0; c<26; c++){
int nxtj = k;
while (nxtj && a[nxtj+1]!=c) nxtj = next[nxtj];
if (a[nxtj+1] == c) nxtj++;
f[i+1][c][nxtj] += f[i][j][k]*PP;
}
}
double val = 0;
for (int j=0; j<26; j++)
for (int k=0; k<n; k++)
val += f[L][j][k];
return 1.0-val;
}
int main(){
int test;
scanf("%d", &test);
for (int T=1; T<=test; T++){
double ans = 0;
scanf("%d %d", &n, &L);
for (int i=1; i<=n; i++){
scanf("%s", s+1);
ans += solve(s);
}
printf("%.6lf\n", ans+eps);
}
return 0;
}
------------AC自动机------------
用n个字符串建一个自动机,单词结尾出的节点存下单词编号。
然后从根节点开始走,
dp[i][j][k]表示走i步, 走到j, 经过了k这些单词, k是二进制数
最后答案等于 sigma(dp[L][j][k] * bit(k)), bit(k)表示k中1的个数。