ac自动机总结

//ac自动机加矩阵快速幂计算没有出现过·的字符串数

题意:给m个病毒字符串,问长度为n的DNA片段有多少种没有包含病毒串的。

参考:http://www.matrix67.com/blog/archives/276

首先解决这个问题:给定一个有向图,问从A点恰好走k步(允许重复经过边)到达B点的方案数mod p的值

    把给定的图转为邻接矩阵,即A(i,j)=1当且仅当存在一条边i->j。令C=A*A,那么C(i,j)=ΣA(i,k)*A(k,j),实际上就等于从点i到点j恰好经过2条边的路径数(枚举k为中转点)。类似地,C*A的第i行第j列就表示从i到j经过3条边的路径数。同理,如果要求经过k步的路径数,我们只需要二分求出A^k即可。

首先考虑长度为n的DNA可以由长度为n-1的加上一个字母得来,可以把长度为n的不含这些片段的DNA分成几类,并建立递推关系。
例如不含ATC,AAA,GGC,CT的DNA可以分成以下几类:
后缀是?A的、后缀是AT的、后缀是AA的、后缀是?G的、后缀是GG的、后缀是?C的,以及后缀是??的(?表示其他任意字母,这些类都是不重叠的,也就是说?A可以是AC,AT,AG,而不能是AA)之所以分成这些类是为了建立它们之间的递推关系,比如?A转化到AT有1种方法(后面加个T),转化到AA有1种方法(后面加个A),转化到?G有1种方法(后面加个G),转化到?C有1种方法(后面加个C),由于?A无论加哪个字母都能转化到一种情况,所以?A不能转化到??,也就是说我们可以求出每种情况与其他各种情况的转化关系,剩下的方法数就是转化到??的。最后我们要求的是从??转化到所有情况的方法数。
这些转化关系可以用AC自动机求出。由于AC自动机的fail指针可以指向与当前串后缀相同的最长字符串尾节结点,那么当前结点就可以转化到这个结点的后继结点。

上述做法的思想就是:从一个合法的字符串后缀,转移到另外一个合法的字符串后缀。如果存在这种转移,那么就在这两个状态之间连接一条边(建图)。然后问题就转化成了第一个提出的问题。

代码说明:

val值表示当前节点是否为病毒串(即后缀是否为病毒串)

建AC自动机的时候,将所有的失配节点的fail指针省略了,直接连接了一条边(因此将失配边和其他边等同看待了),最终的Trie树将变成一个有向图。

对于自动机代码的细节可以参考刘汝佳的《训练指南》。

注意自动机的两个注释。因为当前结点可能不是一个病毒串的终点,但是其失配边指向的串是个病毒,说明当前串的后缀也是个病毒串。


 

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <iostream>
#include <queue>
using namespace std;
typedef long long ll;
 
struct AC_Automata {
    #define Nn 102
    #define M 4
    int ch[Nn][M], val[Nn], f[Nn], last[Nn], sz;
    void clear() { sz = 1; memset(ch[0], 0, sizeof(ch[0])); }
    int idx(char c) {
        if (c == 'A') return 0;
        if (c == 'C') return 1;
        if (c == 'T') return 2;
        return 3;
    }
 
    void insert(char s[], int v) {
        int u = 0;
        for (int i=0; s[i]; i++) {
            int c = idx(s[i]);
            if (!ch[u][c]) {
                memset(ch[sz], 0, sizeof(ch[sz]));
                val[sz] = 0;
                ch[u][c] = sz++;
            }
            u = ch[u][c];
        }
        val[u] = 1;  ///标记当前节点是病毒串
    }
    void build() {
        queue<int> q;
        f[0] = 0;
        for (int c=0; c<M; c++) {
            int u = ch[0][c];
            if (u) { f[u] = last[u] = 0; q.push(u); }
        }
        while (!q.empty()) {
            int r = q.front(); q.pop();
            for (int c=0; c<M; c++) {
                int u = ch[r][c];
                if (!u) {
                    ch[r][c] = ch[f[r]][c];
                    val[r] = val[r] || val[f[r]];  ///如果失配边指向的结点是病毒,那么当前串的后缀也是病毒串
                    continue;
                }
                q.push(u);
                f[u] = ch[f[r]][c];
                last[u] = val[f[u]] ? f[u] : last[f[u]];
            }
        }
    }
} ac;
#define Mod 100000ll
#define N 200
ll a[N][N];
int n;
//c = a*b
void Multi(ll a[][N], ll b[][N], ll c[][N]) {
    for (int i=0; i<n; i++)
        for (int j=0; j<n; j++) {
            c[i][j] = 0;
            for (int k=0; k<n; k++)
                c[i][j] = (c[i][j] + a[i][k]*b[k][j]) % Mod;
        }
}
//d = s
void copy(ll d[][N], ll s[][N]) {
    for (int i=0; i<n; i++) for (int j=0; j<n; j++)
        d[i][j] = s[i][j];
}
//a = a^b % Mod
void PowerMod(ll a[][N], ll b) {
    ll t[N][N], ret[N][N];
    for (int i=0; i<n; i++) ret[i][i] = 1;
    while (b) {
        if (b & 1) { Multi(ret, a, t); copy(ret, t); }
        Multi(a, a, t); copy(a, t);
        b >>= 1;
    }
    copy(a, ret);
}
void init() {
    n = ac.sz;
    int u;
    memset(a, 0, sizeof(a));
    for (int i=0; i<n; i++) if (!ac.val[i]) {
        for (int j=0; j<4; j++) {
            u = ac.ch[i][j];
            if (!ac.val[u]) a[i][u]++;
        }
    }
}
int main() {
    char s[12];
    int m;
    ll b;
    while (scanf("%d %lld", &m, &b) == 2) {
        ac.clear();
        for (int i=1; i<=m; i++) {
            scanf(" %s", s); ac.insert(s, i);
        }
        ac.build();
        init();
        PowerMod(a, b);
 
        ll sum = 0;
        for (int i=0; i<n; i++) sum = (sum + a[0][i]) % Mod;
        cout << sum << endl;
    }
 
    return 0;
}

//ac自动机加dp统计符合条件的个数 BZOJ1030

f[i][j]表示到第i位匹配到第j个节点

//JSOI交给队员ZYX一个任务,编制一个称之为“文本生成器”的电脑软件:该软件的使用者是一些低幼人群,他们现在使用的是GW文本生成器v6版。该软件可以随机生成一些文章―――总是生成一篇长度固定且完全随机的文章—— 也就是说,生成的文章中每个字节都是完全随机的。如果一篇文章中至少包含使用者们了解的一个单词,那么我们说这篇文章是可读的(我们称文章a包含单词b,当且仅当单词b是文章a的子串)。但是,即使按照这样的标准,使用者现在使用的GW文本生成器v6版所生成的文章也是几乎完全不可读的。 ZYX需要指出GW文本生成器 v6生成的所有文本中可读文本的数量,以便能够成功获得v7更新版。你能帮助他吗?
//#include<iostream>
#include<cstdio>
#include<cstring>
#define mod 10007
using namespace std;
int n,m,sz=1,ans1,ans2=1;
int a[6001][27],point[6001],q[6001],f[101][6001];
char s[101];
bool danger[6001];
void ins()
{
	int now=1,c;
	for(int i=0;i<strlen(s);i++)
	{
		c=s[i]-'A'+1;
		if(a[now][c])now=a[now][c];
		else now=a[now][c]=++sz;
	}
	danger[now]=1;
}
void acmach()
{
	int t=0,w=1,now;
	q[0]=1;point[1]=0;
	while(t<w)
	{
		now=q[t++];
		for(int i=1;i<=26;i++)
		{
			if(!a[now][i])continue;
			int k=point[now];
			while(!a[k][i])k=point[k];
			point[a[now][i]]=a[k][i];
			if(danger[a[k][i]])
			   danger[a[now][i]]=1;
            q[w++]=a[now][i];
		}
	}
}
void dp(int x)
{
	for(int i=1;i<=sz;i++)
	{
		if(danger[i]||!f[x-1][i])continue;
		for(int j=1;j<=26;j++)
		{
			int k=i;
			while(!a[k][j])k=point[k];
			f[x][a[k][j]]=(f[x][a[k][j]]+f[x-1][i])%mod;
		}
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=26;i++)a[0][i]=1;
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s);
		ins();
	}
	acmach();
	f[0][1]=1;
	for(int i=1;i<=m;i++)dp(i);
	for(int i=1;i<=m;i++)
	   ans2=(ans2*26)%mod;
    for(int i=1;i<=sz;i++)
       if(!danger[i])ans1=(ans1+f[m][i])%mod;
    printf("%d",(ans2-ans1+mod)%mod);
	return 0;
}


 

作者:wwwiskey 
来源:CSDN 
原文:https://blog.csdn.net/yang_7_46/article/details/9884933 
版权声明:本文为博主原创文章,转载请附上博文链接!

//

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值