BZOJ4861【BJOI2017】魔法咒语题解(AC自动机+DP+矩阵乘法)

题目:BZOJ4861.
题目大意:给定 n n n个串 S i S_i Si m m m个串 T i T_i Ti,现在要用这 S i S_i Si个串(可重复使用)拼成一个长度为 l l l的串使得拼成的串所有 T i T_i Ti均未出现,求方案数.注意只要组成方式不同就算不同的方案.
1 ≤ n , m ≤ 50 , 1 ≤ ∑ ∣ S i ∣ , ∑ ∣ T i ∣ ≤ 100 , 1 ≤ l ≤ 1 0 8 1\leq n,m\leq 50,1\leq \sum |S_i|,\sum |T_i|\leq 100,1\leq l\leq 10^8 1n,m50,1Si,Ti100,1l108.
数据分成两档,第一档满足 1 ≤ l ≤ 100 1\leq l\leq 100 1l100,第二档满足 1 ≤ ∣ S i ∣ ≤ 2 1\leq |S_i|\leq 2 1Si2.

一题正解分两段是真的恶心,强行二合一啊…

首先,看到这种套路题肯定先把所有 T i T_i Ti放进Trie里建AC自动机.

然后考虑一个DP,设 f [ i ] [ j ] f[i][j] f[i][j]表示构造一个长度为 i i i的串,在AC自动机中运行到点 j j j,且不能有任何一个 T i T_i Ti出现的方案数.

现在我们考虑转移,显然可以枚举每一个串 S k S_k Sk,若在AC自动机上从状态 j j j开始运行串 S k S_k Sk到达点 t t t,则 f [ i ] [ j ] f[i][j] f[i][j]可以转移到 f [ i + ∣ S k ∣ ] [ t ] f[i+|S_k|][t] f[i+Sk][t].

也就是说:
f [ i ] [ j ] → f [ i + ∣ S k ∣ ] [ t = j → S k ] f[i][j]\rightarrow f[i+|S_k|][t=j\rightarrow S_k] f[i][j]f[i+Sk][t=jSk]

我们先处理出所有不能走到的点(每个 T i T_i Ti代表的状态在fail树上的整棵子树),在运行时若走到了这么一个点就直接把贡献设为 0 0 0就好了.

发现这个算法的时间复杂度为 O ( l ∑ ∣ T i ∣ ∑ ∣ S i ∣ ) O(l\sum |T_i|\sum |S_i|) O(lTiSi),可以通过第一档数据.

那么第二档数据呢?我们发现这个时候 l l l很大,但是 S i S_i Si的长度均小于等于 2 2 2,这不是明摆着矩乘么…

但是若 S i S_i Si的长度为 1 1 1还好做, S i S_i Si的长度为 2 2 2怎么做?

其实很简单,原来我们的状态矩阵只记录了当前串长为 i i i时的信息,现在只需要多记录串长为 i − 1 i-1 i1时的信息就好了.

时间复杂度 O ( ( ∑ ∣ T i ∣ ) 3 log ⁡ l ) O((\sum |T_i|)^3\log l) O((Ti)3logl),可以通过第二档数据.

代码如下:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=50,M=100,C=26,mod=1000000007;

int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
int sub(int a,int b){return a-b<0?a-b+mod:a-b;}
int mul(int a,int b){return (LL)a*b%mod;}
void sadd(int &a,int b=1){a=add(a,b);}
void ssub(int &a,int b=1){a=sub(a,b);}
void smul(int &a,int b){a=mul(a,b);}

int n,m,sk,len[N+9];
char s[N+9][M+9],t[M+9];
struct Trie{
  int s[C],fail,cnt;
}tr[M+9];
int cn;

void Build(){cn=1;}

int Insert(char *s,int n){
  int k=1;
  for (int i=1;i<=n;++i)
    if (tr[k].s[s[i]-'a']) k=tr[k].s[s[i]-'a'];
    else tr[k].s[s[i]-'a']=++cn,k=cn;
  ++tr[k].cnt;
  return k;
}

queue<int>q;

void Bfs_fail(){
  tr[1].fail=1;
  for (int i=0;i<C;++i)
    if (tr[1].s[i]) tr[tr[1].s[i]].fail=1,q.push(tr[1].s[i]);
    else tr[1].s[i]=1;
  while (!q.empty()){
  	int t=q.front();q.pop();
  	tr[t].cnt+=tr[tr[t].fail].cnt;
  	for (int i=0;i<C;++i)
  	  if (tr[t].s[i]) tr[tr[t].s[i]].fail=tr[tr[t].fail].s[i],q.push(tr[t].s[i]);
  	  else tr[t].s[i]=tr[tr[t].fail].s[i];
  }
}

int Judge(int st,char *s,int n){
  if (tr[st].cnt) return 0;
  int k=st;
  for (int i=1;i<=n;++i){
    k=tr[k].s[s[i]-'a'];
    if (tr[k].cnt) return 0;
  }
  return k;
}

int dp1[M+9][M+9],ans;
struct matrix{
  
  int n,m,v[M*2+9][M*2+9];
  
  matrix operator * (const matrix &p)const{
  	matrix res=matrix();
  	res.n=n;res.m=p.m;
  	for (int i=1;i<=n;++i)
  	  for (int j=1;j<=m;++j)
  	    for (int k=1;k<=p.m;++k)
  	      sadd(res.v[i][k],mul(v[i][j],p.v[j][k]));
  	return res;
  }
  
}dp2,go;

void Build_dp2(){
  dp2.n=1;dp2.m=cn<<1;
  dp2.v[1][cn+1]=1;
  for (int i=1;i<=n;++i)
    if (len[i]==1){
      int td=Judge(1,s[i],1);
      if (!td) continue;
      sadd(dp2.v[1][td]);
    }
}

void Build_go(){
  go.n=go.m=cn<<1;
  for (int i=1;i<=n;++i)
    if (len[i]==1){
	  for (int j=1;j<=cn;++j){
        int td=Judge(j,s[i],1);
        if (td) sadd(go.v[j][td]);
      }
    }else{
      for (int j=1;j<=cn;++j){
      	int td=Judge(j,s[i],2);
      	if (td) sadd(go.v[j+cn][td]);
      }
    }
  for (int i=1;i<=cn;++i) go.v[i][i+cn]=1;
}

matrix E(int n){
  matrix res=matrix();
  res.n=res.m=n;
  for (int i=1;i<=n;++i) res.v[i][i]=1;
  return res;
}

matrix Power(matrix a,int k){
  matrix res=E(a.n);
  for (;k;k>>=1,a=a*a)
    if (k&1) res=res*a;
  return res;
}

Abigail into(){
  scanf("%d%d%d",&n,&m,&sk);
  for (int i=1;i<=n;++i){
  	scanf("%s",s[i]+1);
    len[i]=strlen(s[i]+1);
  }
  Build();
  for (int i=1;i<=m;++i){
  	scanf("%s",t+1);
  	Insert(t,strlen(t+1)); 
  }
}

Abigail work1(){
  Bfs_fail();
  dp1[0][1]=1;
  for (int i=0;i<sk;++i)
    for (int j=1;j<=cn;++j){
      if (!dp1[i][j]) continue;
	  for (int k=1;k<=n;++k){
	  	if (i+len[k]>sk) continue;
      	int td=Judge(j,s[k],len[k]);
      	if (!td) continue;
      	sadd(dp1[i+len[k]][td],dp1[i][j]);
      }
    }
  for (int i=1;i<=cn;++i) sadd(ans,dp1[sk][i]);
}

Abigail work2(){
  Bfs_fail();
  Build_dp2();
  Build_go();
  dp2=dp2*Power(go,sk-1);
  for (int i=1;i<=cn;++i) sadd(ans,dp2.v[1][i]);
}

Abigail outo(){
  printf("%d\n",ans);
}

int main(){
  into();
  if (sk<=M) work1();
  else work2();
  outo();
  return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值