dtoj1721. 字符串生成器 ( strgen )

1721. 字符串生成器 ( strgen )

有一个字符串生成器,初始时生成的字符串为空串,它每次按照给定概率随机生成一个小写字母,加在当前已生成字符串的后面。

给定N个长度为L的字符串,每个字符串由小写字母组成。
如果在某个时候,发现每个给定字符串都在当前已生成的字符串中作为子串出现过,生成器就会停下来,将当前生成的字符串作为输出。
求输出字符串长度的期望值。


Sol

考虑AC自动机。

我们设f[k][S]表示当前走到的节点是k,已经完成的串为集合S,到最终状态的期望步数。

那么可以发现f[k][S]是若干方程。

我们按S分层,那么层与层之间的边是单向的,只有S连向S的子集。

那么我们可以一层层高斯消元,对于跨层的边我们先算出上一层的答案,这层中当成常数就行。

这题卡精!!!

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define maxn 55
#define eps 1e-7
using namespace std;
int T,n,l,c,tr[maxn][10],tot,fail[maxn];
int id[maxn];
double p[10],a[maxn][maxn],f[maxn][1<<10],ans[maxn];
char ch[maxn];
void Q(){
    for(int i=0;i<=tot;i++){
        fail[i]=0;id[i]=0;
        for(int j=0;j<c;j++)tr[i][j]=0;
        for(int j=0;j<(1<<n);j++)f[i][j]=0;
    }
    tot=0;
}
bool ins(int x){
    int k=0;
    for(int i=0;i<l;i++){
        if(!tr[k][ch[i]-'a'])tr[k][ch[i]-'a']=++tot;
        k=tr[k][ch[i]-'a'];
    }
    if(id[k])return 0;
    id[k]=1<<(x-1);return 1;
}
queue<int>q;
void build(){
    for(int i=0;i<c;i++)if(tr[0][i])q.push(tr[0][i]);
    while(!q.empty()){
        int k=q.front();q.pop();
        for(int i=0;i<c;i++){
            if(tr[k][i])fail[tr[k][i]]=tr[fail[k]][i],q.push(tr[k][i]);
            else tr[k][i]=tr[fail[k]][i];
        }
    }
}
void Guess(){
    int N=tot;
    for(int i=0;i<=N;i++){
        int Max=i;
        //for(int j=i;j<=N;j++)if(fabs(a[j][i])>fabs(a[Max][i]))Max=j;
        for(int j=i;j<=N+1;j++)swap(a[i][j],a[Max][j]);
        if(fabs(a[i][i])<eps)continue;
        for(int j=i+1;j<=N;j++){
            double tmp=a[j][i]/a[i][i];
            for(int k=i;k<=N+1;k++)a[j][k]-=a[i][k]*tmp;
        }
    }
    for(int i=N;i>=0;i--){
        ans[i]=-a[i][N+1]/a[i][i];
        for(int j=i-1;j>=0;j--){
            a[j][N+1]+=a[j][i]*ans[i];
        }
    }
}
void work(){
    Q();
    scanf("%d%d%d",&n,&l,&c);
    for(int i=1;i<=n;i++){
        scanf(" %s",ch);
        if(!ins(i))n--,i--;
    }
    build();
    for(int i=0,t;i<c;i++){
        scanf("%d",&t);p[i]=1.0*t/10000;
        
    }
    int N=(1<<n)-1; 
    
    for(int S=N-1;S>=0;S--){
        for(int i=0;i<=tot;i++)
        for(int j=0;j<=tot+1;j++)a[i][j]=0;
        for(int i=0;i<=tot;i++){
            for(int j=0;j<c;j++){
                int v=tr[i][j];
                a[i][v]+=p[j];
                if(id[v]&&(!(S&id[v]))){
                    a[i][v]-=p[j];
                    a[i][tot+1]+=(f[v][S|id[v]])*p[j];
                }
            }
            a[i][i]--;a[i][tot+1]++;
        }
        Guess();
        for(int i=0;i<=tot;i++)f[i][S]=ans[i];
    }
    printf("%.5lf\n",f[0][0]);
}
int main(){
    for(scanf("%d",&T);T--;work());
    return 0;
}
View Code

 

posted @ 2020-02-02 13:05  liankewei123456  阅读(363)  评论(0编辑  收藏  举报