POJ 2778 AC自动机+矩阵快速幂

题意

有M个字符串,这M个字符串不能出现在字符串中。要求字符串长度为N,问有多少种方案。

题解

首先,对于这道题,需要知道一个数学定理。对于一张图的邻接矩阵,邻接矩阵的N次幂就是两点距离为N的路径条数。
知道了上述定理以后,这道题就可以看作,字典树从0开始,到某个节点,路径长度为N的方案个数。因为存在不能出现的字符串,结合AC自动机便可解决该题。利用AC自动机对不能出现的字符串,以及后缀子串是不能出现的字符串的节点进行标记。(因为在字典树上,如果想从0开始访问这个节点,那就必须要构成危险字符,才能访问这个节点。)在构建矩阵的时候,被标记的节点值为0。
构建矩阵完成后,利用矩阵快速幂方法进行运算。最后矩阵第一行的和即为所求。

补充说明

事实上,把AC自动机的边映射到矩阵上整个过程详细分析的话还是比较难的。大概可以这样理解。例如拿“ACAGAAA”和”ACAG”进行分析。首先的话,第一个字符我们可以选择A或者GCT,这样的话便有矩阵[3,1,0,0,0,0,0,0]。如果我们要选择第二个字符的话,我们可以看一下,每个节点转移到0状态的路径矩阵[3,2,3,1,0,2,2,0]T。两个矩阵相乘,所代表的意义就是从0出发,经过某个点,再回到0。(从0出发是必要的,但是再回到0不是必要的,因此最后相乘的结果存放在第一行的第一列,第一行其他列代表不回到0的情况)这样依此类推,便可以解决问题。
当然,有一些细节还是要说明一下。比如说ACAGAAA,我们可以看到,转移到0状态的路径矩阵为[3,2,3,1,0,2,2,0]T,也就是说倒数第二个A和倒数第三个A也是有能力转移到0点的。这样的话,会不会存在不符合条件的字符串转移到了0点的情况呢
仔细想一下的话,就会发现这种情况是不存在的。因为如果想要转移到0点,首先就要到达这个点,换句话说就是初始第一行[3,1,0,0,0,0,0,0],经过某些变换后,要能使倒数第二个数和倒数第三个数变为1以上,这样的话才有可能进行转移。
我们可以分析一下,我们可以首先打印一下6号点的转移情况(矩阵第七列),看看谁能转移到6号点,我们发现矩阵为[0,0,0,0,0,1,0,0],在这里我们发现,5号点有一条边可以转移到6号点。(也就是我们已经选择了A,再选一个A)然后,我们再打印一下5号点的矩阵,看看谁能转移到5号点,结果发现是[0,0,0,0,0,0,0,0]。我们发现,4号点和5号点有边相连。原本如果处于四号点状态,再选一个A就可以达到5号点。这时候,对四号点进行处理的作用就显现出来了。对四号点进行过滤,使得四号点不能进行任何状态转移,也不允许任何点转移到四号状态,意义就在于此
我们可以尝试对状态矩阵进行一千次快速幂运算,结果正如我们所想的。0-3号点都不为0,4号点以后都为0。这也就意味着,0-3号点,都可以进行转移,组成字符串。
上述就是我对利用矩阵+AC自动机计算字符串组成方案的一些思考,主要就是为了验证矩阵+AC自动机算法的正确性。如有错误,还望赐教。

代码

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define UP(i,l,h) for(int i=l;i
#define DOWN(i,h,l) for(int i=h-1;i>=l;i--)
#define W(a) while(a)
#define MEM(a,b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f3f3f3f3f
#define LL long long
#define MAXN 10000
#define EPS 1e-10
#define MOD 100000
#define N 2

using namespace std;
typedef LL MATRIX[110][110];

int ch[110][4];
char fea[15];
int len,sz;
int val[110];
int f[110];
MATRIX a,temp,ans;


int getNum(char c) {
    if(c=='A') {
        return 0;
    }
    if(c=='C') {
        return 1;
    }
    if(c=='T') {
        return 2;
    }
    if(c=='G') {
        return 3;
    }
}

void insert() {
    len=strlen(fea);
    int u=0;
    UP(i,0,len) {
        int x=getNum(fea[i]);
        if(!ch[u][x]) {
            ch[u][x]=sz++;
        }
        u=ch[u][x];
    }
//    cout<
    val[u]=1;
}

void getFail() {
    queue<int> q;
    MEM(f,0);
    UP(x,0,4) {
        if(ch[0][x]) {
            q.push(ch[0][x]);
        }
    }
    int s=0;
    W(!q.empty()) {
        int r=q.front();
        q.pop();
        UP(x,0,4) {
            int u=ch[r][x];
            if(!u) {
                ch[r][x]=ch[f[r]][x];
                continue;
            }
            q.push(u);
            int v=f[r];
            f[u]=ch[v][x];
            if(val[f[u]]==1) {
                val[u]=1;
            }
        }
    }
}

void buildMatrix() {
    MEM(a,0);
    UP(i,0,sz) {
        UP(j,0,4) {
            if(val[i]||val[ch[i][j]]) {
//                cout<
                continue;
            }
            a[i][ch[i][j]]++;
        }
    }
}

void matrixMulti(MATRIX a,MATRIX b) {
    MEM(temp,0);
    UP(i,0,sz) {
        UP(j,0,sz) {
            UP(k,0,sz) {
                temp[i][j]=(temp[i][j]+a[i][k]*b[k][j])%MOD;
//                cout<
            }
        }
    }
    memcpy(a,temp,sizeof(temp));
}

void quick(int n) {
    MEM(ans,0);
    UP(i,0,sz){
            ans[i][i]=1;
    }
    W(n){
        if(n&1){
            matrixMulti(ans,a);
        }
        matrixMulti(a,a);
        n>>=1;
    }
}

LL getAns(){
    LL sum=0;
    UP(i,0,sz){
        sum+=ans[0][i];
    }
    return sum;
}

int main() {
    int m,n;
    W(~scanf("%d%d",&m,&n)) {
        MEM(val,0);
        MEM(ch,0);
        sz=1;
        UP(i,0,m) {
            scanf("%s",fea);
            insert();
        }
        getFail();
        buildMatrix();
        quick(n);
        printf("%I64d\n",getAns()%MOD);
    }
}

/*
2 20000000000
ACG
C
*/

你可能感兴趣的:(ACM算法问题)