题意:

  有一个字符串生成器,初始时生成的字符串为空串,它每次按照给定概率随机生成一个小写字母,加在当前已生成字符串的后面。
  给定N个长度为L的字符串,每个字符串由小写字母组成。
  如果在某个时候,发现每个给定字符串都在当前已生成的字符串中作为子串出现过,生成器就会停下来,将当前生成的字符串作为输出。
  求输出字符串长度的期望值。

题解:

考虑状压dp。

分层dp,同层间有环,不同层间是DAG。

设$f[i][S] $为从trie图的i节点开始,还需要出现$S$这些字符串的期望长度。

初始条件:

$f[i][0]=0$

转移:

$ f[i][S]=f[i][S-i] $   $(if i为结尾节点且i∈S)$

$ f[i][S] = \sum_{j} f[trie[i][j]][S]*p[j]+1$  $else$

$trie[i][j]$为trie图上的边。(补出fail节点的后继)
然后${S}$从小到大枚举就好了。

(高斯消元掉了发精度。。囧 ..

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
using namespace std;
typedef long double ld;
int tt,n,l,t;
char s[10];
int trie[55][26],pos[55],tot,fail[55];
ld f[55][356],p[26],a[55][55];
queue<int> q;
void mkfail(){
    q.push(0);
    while(!q.empty()){
        int x=q.front();q.pop();
        for(int i=0;i<t;i++){
            if(trie[x][i]){
                if(x) fail[trie[x][i]]=trie[fail[x]][i];
                q.push(trie[x][i]);
            }else trie[x][i]=trie[fail[x]][i];
        }
    }
}
void Gauss(int n){
    for(int i=0;i<=n;i++){
        ld mx=0;int pos;
        for(int j=i;j<=n;j++){
            if(fabs(a[j][i])>mx){
                mx=fabs(a[j][i]);
                pos=j;
            }
        }
        for(int j=i;j<=n+1;j++) swap(a[i][j],a[pos][j]);
        for(int j=i+1;j<=n;j++){
			ld f=a[j][i]/a[i][i];
			for(int k=i;k<=tot+1;k++){
				a[j][k]-=f*a[i][k];
			}
        }
    }
    for(int i=n;i>=0;i--){
        for(int j=i+1;j<=n;j++){
            a[i][tot+1]-=a[i][j]*a[j][tot+1];
        }
        a[i][tot+1]/=a[i][i];
    }
}

void calc(int x){
    memset(a,0,sizeof a);
    for(int i=0;i<=tot;i++){
        a[i][i]=1;
        if(pos[i]!=-1&&((1<<pos[i])&x)) a[i][tot+1]=f[i][x^(1<<pos[i])];
        else{
            a[i][tot+1]=1;
            for(int j=0;j<t;j++){
                a[i][trie[i][j]]-=p[j];
            }
        }
    }
    Gauss(tot);
    for(int i=0;i<=tot;i++) f[i][x]=a[i][tot+1];
}
int main(){
    scanf("%d",&tt);
    while(tt--){
        memset(trie,0,sizeof trie);
        memset(pos,-1,sizeof pos);
        memset(fail,0,sizeof fail);
        memset(f,0,sizeof f);
        tot=0;
        scanf("%d%d%d",&n,&l,&t);
        for(int i=0;i<n;i++){
            scanf("%s",s);
            int id=0;
            for(int j=0;s[j];j++){
                if(trie[id][s[j]-'a']) id=trie[id][s[j]-'a'];
                else id=trie[id][s[j]-'a']=++tot;
            }
            pos[id]=i;
        }
        for(int i=0,x;i<t;i++){
            scanf("%d",&x);
            p[i]=x/10000.0;
        }
        mkfail();
        for(int i=1;i<(1<<n);i++){
            calc(i);
        }
        printf("%.10f\n",(double)f[0][(1<<n)-1]);
    }
    return 0;
}