【BZOJ4861】[Beijing2017]魔法咒语 矩阵乘法+AC自动机+DP

【BZOJ4861】[Beijing2017]魔法咒语

题意:别看BZ的题面了,去看LOJ的题面吧~

题解:显然,数据范围明显的分成了两部分:一个是L很小,每个基本词汇长度未知;一个是L很大,每个基本词汇的长度是1或2。看来只能写两份代码了。

对于L很小的,我们先将禁忌串建成一个AC自动机,然后预处理出to[i][j]表示AC自动机中的第i个节点在加入基本词汇j后会到达的节点。然后设f[i][j]表示总长度为i,匹配到第j个节点的方案数。然后DP一下就好了。

对于L很大的,我们想到矩乘,设ans[i][j]表示总长度为i,匹配到第j个节点的方案数。但是ans[i]这个矩阵由ans[i-1]和ans[i-2]两个矩阵转移过来,所以我们直接用分块矩阵的乘法,即:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
typedef long long ll;
const ll mod=1000000007;
int n,m,N,M,L,mx,sum;
struct mat
{
	ll v[210][210];
	mat (){memset(v,0,sizeof(v));}
	ll* operator [](int a){return v[a];}
	mat operator * (mat a)
	{
		mat ret;
		int i,j,k;
		for(i=1;i<=2*M;i++)	for(j=1;j<=2*M;j++)	for(k=1;k<=2*M;k++)	(ret[i][j]+=v[i][k]*a[k][j])%=mod;
		return ret;
	}
}ans,x;
int l1[60],to[110][60];
ll f[110][110];
queue<int> q;
struct node
{
	int ch[26],fail,cnt;
}p[110];
char s1[60][110],s2[60][110];
void build()
{
	q.push(1);
	int i,j,k,a,u;
	while(!q.empty())
	{
		u=q.front(),q.pop();
		for(i=0;i<26;i++)
		{
			if(!p[u].ch[i])
			{
				if(u==1)	p[u].ch[i]=1;
				else	p[u].ch[i]=p[p[u].fail].ch[i];
				continue;
			}
			q.push(p[u].ch[i]);
			if(u==1)
			{
				p[p[u].ch[i]].fail=1;
				continue;
			}
			p[p[u].ch[i]].fail=p[p[u].fail].ch[i];
			p[p[u].ch[i]].cnt|=p[p[p[u].fail].ch[i]].cnt;
		}
	}
	for(i=1;i<=M;i++)	for(j=1;j<=n;j++)
	{
		u=i,a=strlen(s1[j]);
		if(p[u].cnt)	to[i][j]=-1;
		for(k=0;k<a;k++)
		{
			u=p[u].ch[s1[j][k]-‘a‘];
			if(p[u].cnt)	break;
		}
		if(k==a)	to[i][j]=u;
		else	to[i][j]=-1;
	}
}
void DP()
{
	int i,j,k,a;
	f[0][1]=1;
	for(i=0;i<L;i++)	for(j=1;j<=M;j++)	for(k=1;k<=n;k++)
	{
		if(to[j][k]==-1)	continue;
		a=strlen(s1[k]);
		if(a+i<=L)	(f[a+i][to[j][k]]+=f[i][j])%=mod;
	}
	for(i=1;i<=M;i++)	sum=(sum+f[L][i])%mod;
	printf("%d",sum);
}
void pm(int y)
{
	while(y)
	{
		if(y&1)	ans=ans*x;
		x=x*x,y>>=1;
	}
}
void MM()
{
	int i,j;
	for(i=1;i<=M;i++)
	{
		for(j=1;j<=n;j++)
		{
			if(to[i][j]==-1)	continue;
			if(strlen(s1[j])==1)	x[i][to[i][j]]++;
			else	x[i+M][to[i][j]]++;
		}
		x[i][i+M]++;
	}
	ans[1][1]=1;
	pm(L);
	for(i=1;i<=M;i++)	sum=(sum+ans[1][i])%mod;
	printf("%d",sum);
}
int main()
{
	scanf("%d%d%d",&n,&m,&L);
	int i,j,a,b,u;
	N=1,M=1;
	for(i=1;i<=n;i++)	scanf("%s",s1[i]),a=strlen(s1[i]),mx=max(mx,a);
	for(i=1;i<=m;i++)
	{
		scanf("%s",s2[i]),a=strlen(s2[i]);
		for(u=1,j=0;j<a;j++)
		{
			b=s2[i][j]-‘a‘;
			if(!p[u].ch[b])	p[u].ch[b]=++M;
			u=p[u].ch[b];
		}
		p[u].cnt=1;
	}
	build();
	if(mx<=2)	MM();
	else	DP();
	return 0;
}
时间: 2024-07-30 00:22:02

【BZOJ4861】[Beijing2017]魔法咒语 矩阵乘法+AC自动机+DP的相关文章

hdu 4878 ZCC loves words(AC自动机+dp+矩阵快速幂+中国剩余定理)

hdu 4878 ZCC loves words(AC自动机+dp+矩阵快速幂+中国剩余定理) 题意:给出若干个模式串,总长度不超过40,对于某一个字符串,它有一个价值,对于这个价值的计算方法是这样的,设初始价值为V=1,假如这个串能匹配第k个模式串,则V=V*prime[k]*(i+len[k]),其中prime[k]表示第k个素数,i表示匹配的结束位置,len[k]表示第k个模式串的长度(注意,一个字符串可以多次匹配同意个模式串).问字符集为'A'-'Z'的字符,组成的所有的长为L的字符串,

HDU 2243 考研路茫茫――单词情结 (AC自动机 + dp)

HDU 2243 考研路茫茫――单词情结 题意:给定一些词根,如果一个单词包含有词根,则认为是有效的.现在问长度不超过L的单词里面,有多少有效的单词? 思路:这道题和POJ 2778是同样的思路.POJ 2778是要找出长度为L的单词里面有多少无效的单词.那么根据同样的方法构造矩阵,然后所有无效的单词个数为 A + A^2 + ... + A^l 个.而所有单词的个数为26 + 26^2 + - + 26^l 个.两个减一下即为答案. 矩阵连乘求和:I + A^2 + A^3 + ... + A

HDU 2243 考研路茫茫——单词情结(AC自动机+DP+快速幂)

题目链接 错的上头了... 这题是DNA的加强版,26^1 +26^2... - A^1-A^2... 先去学了矩阵的等比数列求和,学的是第二种方法,扩大矩阵的方法.剩下就是各种模板,各种套. #include <cstdio> #include <cstring> #include <iostream> #include <map> #include <algorithm> #include <vector> #include &l

HDU3341 Lost&#39;s revenge(AC自动机+DP)

题目是给一个DNA重新排列使其包含最多的数论基因. 考虑到内存大概就只能这么表示状态: dp[i][A][C][G][T],表示包含各碱基个数为ACGT且当前后缀状态为自动机第i的结点的字符串最多的数论基因数 其中ACGT可以hash成一个整数(a*C*G*T+c*G*T+g*T+T),这样用二维数组就行了,而第二维最多也就11*11*11*11个. 接下来转移依然是我为人人型,我是丢进一个队列,用队列来更新状态的值. 这题果然挺卡常数的,只好手写队列,最后4500msAC,还是差点超时,代码也

poj 1625 Censored!(AC自动机+DP+高精度)

题目链接:poj 1625 Censored! 题目大意:给定N,M,K,然后给定一个N字符的字符集和,现在要用这些字符组成一个长度为M的字符串,要求不包 括K个子字符串. 解题思路:AC自动机+DP+高精度.这题恶心的要死,给定的不能匹配字符串里面有负数的字符情况,也算是涨姿势 了,对应每个字符固定偏移128单位. #include <cstdio> #include <cstring> #include <queue> #include <vector>

HDU 2296 Ring AC自动机 + DP

题意:给你n个模式串,每个模式串有一个得分,让你构造出一个长度为N之内且分数最高的文本串;输出字典序列最小的. 解题思路:  AC自动机 + DP , 不过要输出字典序列最小,多开一个 一个三维字符串来辅助二维DP(新思路) , DP[i][j] ,表示到i位置状态为j的最大得分. 解题代码: 1 // File Name: temp.cpp 2 // Author: darkdream 3 // Created Time: 2014年09月11日 星期四 15时18分4秒 4 5 #inclu

HDU2296——Ring(AC自动机+DP)

题意:输入N代表字符串长度,输入M代表喜欢的词语的个数,接下来是M个词语,然后是M个词语每个的价值.求字符串的最大价值.每个单词的价值就是单价*出现次数.单词可以重叠.如果不止一个答案,选择字典序最小的. 题解:AC自动机+dp.dp[i][j]表示在字符串长度i,在自动机的第j个状态.因为要字典序最小,所以转移时要保持字典序最小. 想了各种转移姿势 最后还是查了题解 发现可以直接记录前缀转移…… #include <bits/stdc++.h> using namespace std; co

hdu 2296 aC自动机+dp(得到价值最大的字符串)

Ring Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 3180    Accepted Submission(s): 1033 Problem Description For the hope of a forever love, Steven is planning to send a ring to Jane with a rom

hdu 2457 AC自动机+dp

DNA repair Time Limit: 5000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 2004    Accepted Submission(s): 1085 Problem Description Biologists finally invent techniques of repairing DNA that contains segments c