题意:给出一些字符和对应的选择概率,随机选择L次后得到一个长度为L的随机字符串S。给k个模板串,计算S不包含任何一个串的概率。
思路:AC自动机+概率dp。之前我做这题一直WA,然后稍微改改代码交了下杭电上类似的题目居然1A了。。后来我才发现原因,是搜到字典树中间非叶子节点了以后,也应该试着往回跳,看看能不能跳到某个模板的末尾。但是杭电那题只有一个模板,所以才A掉的。不过杭电的题目背景比较有意思,好像是一个我小时候听过的故事,如果一个猴子随机敲键盘,可以敲出世界上任何一篇文章。。。
回到正题。这题先建立一个AC自动机,然后从字典树树根开始,走L步,如果走到模板串的末尾,就不能再走下去了,边走边算概率。我是用递推dp来做的,dp(i,j)表示走到字典树的第i个节点,已经走了j步的概率。毕竟dp(i,j)有多条路线可以到达,全部加在一起算比较省时间,如果一条路走L步走到底会超时的。要注意的是,走到某个节点时,它本身不是模板串末尾并不代表可以继续走下去,应该往回跳看看是否会遇到模板串的末尾。最后结果是sum(dp(i,L))(i取所有的树节点)。
#include <iostream>
#include <stdio.h>
#include <cmath>
#include <algorithm>
#include <iomanip>
#include <cstdlib>
#include <string>
#include <memory.h>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <ctype.h>
#define INF 1<<30
#define ll long long
#define max3(a,b,c) max(a,max(b,c))
using namespace std;
#define maxnode 1010
int T; //50
int k; //20;
int n; //
int L;
bool valid[65];
double p[65];
double dp[maxnode][110];
int char2int(char c){
if(c>='a'&&c<='z')return c-'a';
if(c>='A'&&c<='Z')return c-'A'+26;
if(c>='0'&&c<='9')return c-'0'+52;
return -1;
}
// ac
int val[maxnode];
int vis[maxnode][110];
int ch[maxnode][65];
int next[maxnode];
int sz;
double ans;
void init(){
sz=1;
memset(ch[0],0,sizeof(ch[0]));
memset(next,0,sizeof(next));
memset(val,0,sizeof(val));
}
void insert(char* str){
int u=0;
int len=strlen(str);
for(int i=0;i<len;i++){
int v=char2int(str[i]);
if( !ch[u][v] ){
ch[u][v]=sz;
memset(ch[sz],0,sizeof(ch[sz]));
val[sz]=0;
sz++;
}
u=ch[u][v];
}
val[u]++;
}
void build_ac(){
memset(next,0,sizeof(next));
queue<int> que; que.push(0);
while(!que.empty()){
int u=que.front(); que.pop();
for(int i=0;i<62;i++){
int v=ch[u][i];
if(!v)continue;
if(!u){
next[v]=0;
que.push(v);
}else{
int k=next[u];
while(k&&!ch[k][i]){
k=next[k];
}
if(ch[k][i])k=ch[k][i];
next[v]=k;
//cout<<"next "<<v<<" = "<<k<<endl;
que.push(v);
}
}
}
}
//ac end
int main(){
scanf("%d",&T);
int cas=0;
while(T--){
cas++;
memset(valid,0,sizeof(valid));
memset(vis,0,sizeof(vis));
ans=0;
init();
//
scanf("%d",&k);
for(int i=1;i<=k;i++){
char pattern[30];
scanf("%s",pattern);
insert(pattern);
}
build_ac();
//
scanf("%d",&n);
for(int i=1;i<=n;i++){
char a;
double b;
cin>>a>>b;
valid[char2int(a)]=1;
p[char2int(a)]=b;
}
//
scanf("%d",&L);
for(int i=0;i<maxnode;i++){
for(int j=0;j<=L;j++)dp[i][j]=0.0;
}
dp[0][0]=1.0; vis[0][0]=1;
for(int i=1;i<=L;i++){
for(int j=0;j<maxnode;j++){
if(!vis[j][i-1])continue;
for(int k=0;k<62;k++){
if(!valid[k])continue;
int v=j;
while(v&&!ch[v][k]){
v=next[v];
}
if(ch[v][k])v=ch[v][k];
int tmp=v;
bool skip=0;
while(tmp){
if(val[tmp]){
skip=1;
break;
}
tmp=next[tmp];
}
if(skip)continue;
dp[v][i]+=dp[j][i-1]*p[k];
vis[v][i]=1;
}
}
}
for(int i=0;i<maxnode;i++){
ans+=dp[i][L];
}
printf("Case #%d: %.6lf\n",cas,ans);
}
return 0;
}
本文介绍了一种结合AC自动机与概率动态规划的方法,用于解决特定字符串匹配问题。通过构建AC自动机并运用概率DP求解随机字符串不包含给定模板串的概率。
740

被折叠的 条评论
为什么被折叠?



