[beijing2013]禁忌 解题报告

这题非常奇怪。。

先说做法。
首先我们考虑一个字符串受到的伤害的最大值,实际上我们把这个字符串中能匹配上的子串看作线段,那就成了经典的线段覆盖问题:选若干个不相交的子串,最多能选多少个?
这样就可以贪心了,表现在ac自动机上的话就是如果到了一个节点,它或它在fail树上的祖先有一个节点是一个子串的末尾,那么到它就等价于直接回根。
然后根据期望的线性性质,期望就是每一步到这种节点的概率和。

奇怪的精度。。
我感觉题面里那种spj的方式是比较科学的(怎么搞都能过XD),bzoj上kac的spj就非常不科学(!!)。。
因为如果矩阵乘的时候最后一个循环是正着循环就跟数据一模一样。。而如果最后一个循环是倒着循环就跟数据差很多。这个应该是C++ long double自己精度的问题。。我搞了很久也只能得出这个结论了。。
所以说直接跟1e-6比是非常不科学的!

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#include<algorithm>
const int N=5+2,S=26+5,L=15+2,A=N*L;
int ac[A][S],fail[A],atot=1;
bool end[A];
typedef long double LLF;
LLF ans[A][A],mat[A][A],tmp[A][A];
int s;
void out(LLF mat[A][A]){
    for(int i=0;i<=atot;++i){
        for(int j=0;j<=atot;++j)printf("%.15f ",(double)mat[i][j]);
        puts("");
    }
    puts("");
}
int len;
void multi(LLF a[A][A],LLF b[A][A]){
    memset(tmp,0,sizeof(tmp));
    for(int i=atot;~i;--i)
        for(int j=atot;~j;--j)
            for(int k=0;k<=atot;++k)
                tmp[i][j]+=b[i][k]*a[k][j];
    memcpy(a,tmp,sizeof(tmp));
}
int main(){
    freopen("taboo.in","r",stdin);
    freopen("taboo.out","w",stdout);
    int n,l;
    char str[L];
    scanf("%d%d%d",&n,&len,&s);
    //build acmachine
    for(int i=n;i--;){
        int node=0;
        scanf("%s",str);
        l=strlen(str);
        for(int j=0;j<l&&!end[node];++j){
            //cout<<str[j]<<"(";
            str[j]-='a';
            if(!ac[node][str[j]])ac[node][str[j]]=atot++;
            node=ac[node][str[j]];
            //cout<<node<<") ";
        }
        end[node]=1;
        //puts("");
    }
    int h=0,t=0;
    int q[A];
    for(int i=s;i--;)
        if(ac[0][i])
            q[t++]=ac[0][i];
    for(;h!=t;++h){
        if(end[fail[q[h]]])end[q[h]]=1;
        for(int i=s;i--;)
            if(ac[q[h]][i]){
                fail[ac[q[h]][i]]=ac[fail[q[h]]][i];
                q[t++]=ac[q[h]][i];
            }
            else ac[q[h]][i]=ac[fail[q[h]]][i];
    }
    //build matrix
    for(int i=atot;i--;)
        if(!end[i])
            for(int j=s;j--;)
                if(end[ac[i][j]]){
                    mat[atot][i]+=1/(LLF)s;
                    mat[0][i]+=1/(LLF)s;
                }
                else{
                    mat[ac[i][j]][i]+=1/(LLF)s;
                    //cout<<"("<<ac[i][j]<<","<<i<<")\n";
                }
    mat[atot][atot]=1;
    for(int i=atot;~i;--i)ans[i][i]=1;

    //out(mat);
    //multi matrix
    for(;len;len>>=1,multi(mat,mat))
        if(len&1)
            multi(ans,mat);
    //out(mat);
    printf("%.10f\n",(double)ans[atot][0]);
}

总结:
①double读入要用%lf,输出用%f。
②ac自动机fail指针指向的是有最长相同后缀的。

你可能感兴趣的:(AC自动机)