【AC自动机+DP】匹配(match)

匹配

【题目大意】给定k个字符串以及长度为n的母串的可选字母的集合,问母串要完整出现给定的k个字符串的方案数,答案模1000000007,字符仅包含小写字母。

(n<=100,m<=10,k<=8,给定字符串长度<=30)


【题解】本来这题是道较水的题,由于刚学了一种更快的建AC自动机的方法,就拿这个练练手。

对给定的k个字符串建立AC自动机。设f[i][j][k]为当前做到第i位(总共要N位),在自动机上的j节点,已经匹配了状态为k的字符串(k为二进制表示)的方案数。


f[i][j][k]可以转移到f[i+1][_j][_k],(_j = t[j].son[p],_k = k or t[_j].stat,p为枚举的下一个字符)。


若是j节点没有字符p这个儿子,则我们根据自动机的性质,就要在j节点的fail中(迭代)找到一个有字符p作为儿子的节点,并且要跳到那个节点上(这样可以保证匹配性)。这个操作在这种新的建ac自动机的方法中被完美解决。


以往代码:

void Construct_fail()
{
	static int d[maxm];
	int l = 0, r = 1;
	while (l < r)
	{
		int x = d[++l];
		fo(i,0,25)
			if (trie[x].son[i])
			{
				int v = trie[x].son[i];
				if (x == 0) trie[v].fail = 0;
				else
				{
					int p = trie[x].fail;
					while (p && !trie[p].son[i]) p = trie[p].fail;
					if (trie[p].son[i]) trie[v].fail = trie[p].son[i];
					else trie[v].fail = 0;
				}
				d[++r] = v;
			}
	}
}

新方法:

void Construct_fail()
{
	static int d[3*maxn];
	int l = 0, r = 1;
	while (l < r)
	{
		int x = d[++l];
		fo(p,1,M)
		{
			int i = a[p];
			if (t[x].son[i])
			{
				int v = t[x].son[i];
				if (x == 0) t[v].fail = 0;
				else t[v].fail = t[t[x].fail].son[i];
				t[v].stat |= t[t[v].fail].stat;
				d[++r] = v;
			} else t[x].son[i] = t[t[x].fail].son[i];
		}
	}
}

(p循环是具体题目的要求)

可以发现新的构造方法省去了一个while循环,若节点x没有i这个儿子,我们直接将t[x].son[i] = t[t[x].fail].son[i];因为当我们匹配到x节点时,如果下一个字符是i,我们也会在x的fail中(迭代)找一个有i儿子的节点。因此直接赋值是正确的,而这个操作也让我们可以直接t[v].fail = t[t[x].fail].son[i](可以优化些许复杂度)。

完整代码:

#include<cstdio>
#include<cstring>
#define fo(i,a,b) for (int i = a;i <= b;i ++)

using namespace std;

const int maxn = 105;
const int P = 1000000007;

int N,M,K,len,tot;
int f[maxn][3*maxn][1<<8];
int a[35],exist[35];
char s[35];

struct node
{
	int son[26];
	int fail,stat;
}t[3*maxn];

void Insert(int x,int i,int id)
{
	int v = s[i] - 'a';
	if (!t[x].son[v]) t[x].son[v] = ++tot;
	if (i != len) Insert(t[x].son[v],i+1,id);
	else t[t[x].son[v]].stat = 1 << (id-1);
}

void Make_trie()
{
	scanf("%d%d",&N,&K);
	fo(i,1,K)
	{
		scanf("%s",s+1);
		len = strlen(s+1);
		Insert(0,1,i);
	}
}

void Construct_fail()
{
	static int d[3*maxn];
	int l = 0, r = 1;
	while (l < r)
	{
		int x = d[++l];
		fo(p,1,M)
		{
			int i = a[p];
			if (t[x].son[i])
			{
				int v = t[x].son[i];
				if (x == 0) t[v].fail = 0;
				else t[v].fail = t[t[x].fail].son[i];
				t[v].stat |= t[t[v].fail].stat;
				d[++r] = v;
			} else t[x].son[i] = t[t[x].fail].son[i];
		}
	}
}

void Deal_matchstring()
{
	int x;
	scanf("%d",&x);
	scanf("%s",s+1);
	fo(i,1,x)
		if (!exist[s[i]-'a'])
		{
			exist[s[i]-'a'] = 1;
			a[++M] = s[i] - 'a';
		}
}

void DP()
{
	f[0][0][0] = 1;
	fo(i,0,N-1)
		fo(j,0,tot)
			fo(k,0,(1<<K)-1)
				if (f[i][j][k])
					fo(p,1,M)
					{
						int _j = t[j].son[a[p]];
						int _k = k | t[_j].stat;
						f[i+1][_j][_k] = (f[i+1][_j][_k] + f[i][j][k]) % P;
					}
}

void Print()
{
	int ans = 0;
	fo(i,0,tot) ans = (ans + f[N][i][(1<<K)-1]) % P;
	printf("%d\n",ans);
}

int main()
{
	freopen("match.in","r",stdin);
	freopen("match.out","w",stdout);
	Make_trie();
	Deal_matchstring();
	Construct_fail();
	DP();
	Print();
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值