ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

[BJOI2017]魔法咒语

2022-03-18 19:33:04  阅读:194  来源: 互联网

标签:int 优化 void 魔法 矩阵 BJOI2017 咒语 快速 dp


descibtion

给你\(n\)个元素串和m个诅咒串,问你能构造长度为\(L\)的字符串的方案数,字符串由元素串拼接而成且不包含诅咒串。
\(n<=50,m<=50,元素串总长和诅咒串总长均<=100,L<=10^8\)
但是这是一道需要数据点分治的题,具体看下面的数据范围

Solution

  • 前60pt,很简单就AC自动机上dp。标记不能到的节点,转移的时候,枚举每个串(复杂度是总长度<=100)。具体dp[i][j](前i位,在j号节点)
  • 后40pt,我发现我骨子里就一直对矩阵快速幂没有印象。我就在想数学怎么容斥+dp,跟本没想到矩阵快速幂优化dp的常见思路。
    能快速幂的核心在于每次\(dp[i][u]->dp[i+1][v]\)对于第二维都是一样的,跟第一维i无关
    然后就可以开心地构矩阵了。
    咦~len元素串<=2?那就存\([dp_{i,0}..dp_{i,nd},dp_{i+1,0}...dp_{i+1,nd}]\)两个状态,这样就可以递推i-1/i-2->i
    矩阵构造也喵喵子:(贺张图)

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=205;
const int mod=1e9+7;
int cnt[N],sz[N],n,m,L,go[N][27],nd;
char s[105][105];
void Insert(int p,bool bd) {
	int u=0;sz[p]=strlen(s[p]+1);
	for(int i=1;i<=sz[p];i++) {
		int x=s[p][i]-'a';
		if(!go[u][x])go[u][x]=++nd;
		u=go[u][x];
	}
	cnt[u]+=bd;
}
int Q[N],hd=1,tl,fail[N];
void gt_fail() {
	for(int i=0;i<26;i++)if(go[0][i])Q[++tl]=go[0][i];
	while(hd<=tl) {
		int u=Q[hd++];cnt[u]+=cnt[fail[u]];
		for(int i=0;i<26;i++) {
			if(go[u][i]) fail[go[u][i]]=go[fail[u]][i],Q[++tl]=go[u][i];
			else go[u][i]=go[fail[u]][i];
		}
	}
}
ll dp[N][N];
void DP() {
	dp[0][0]=1;
	for(int i=0;i<L;i++) {
		for(int j=0;j<=nd;j++) {
			if(!dp[i][j])continue;
			for(int k=1;k<=n;k++) {
				if(i+sz[k]>L)continue;
				int u=j;bool f=1;
				for(int d=1;d<=sz[k];d++) {
					int x=s[k][d]-'a';
					u=go[u][x];
					if(cnt[u]||!u){f=0;break;}
				}
				if(f) dp[i+sz[k]][u]=(dp[i+sz[k]][u]+dp[i][j])%mod;
			}
		}
	}
	ll ans=0;
	for(int i=0;i<=nd;i++) ans=(ans+dp[L][i])%mod;
	printf("%lld\n",ans);
}
const int M=N<<1;
int len,id[M];
ll bs[M][M],t[M][M];
void _pw() {
	for(int i=0;i<len;i++) for(int j=0;j<len;j++)t[i][j]=bs[i][j],bs[i][j]=0;
	for(int i=0;i<len;i++) for(int j=0;j<len;j++) {
		if(!t[i][j])continue;
		for(int k=0;k<len;k++) bs[i][k]=(bs[i][k]+t[i][j]*t[j][k])%mod;
	}
}
ll ans[M][M];
void Mul() {
	for(int i=0;i<len;i++) for(int j=0;j<len;j++)t[i][j]=ans[i][j],ans[i][j]=0;
	for(int i=0;i<len;i++) for(int j=0;j<len;j++) {
		if(!t[i][j])continue;
		for(int k=0;k<len;k++) ans[i][k]=(ans[i][k]+t[i][j]*bs[j][k])%mod;
	}
}
void ksm(int b) {
	for(int i=0;i<len;i++)for(int j=0;j<len;j++)ans[i][j]=bs[i][j]; b--;
	for(;b;b>>=1,_pw()){if(b&1)Mul();}
}
int to1[M][M],tot;
void init() {
	for(int j=0;j<=nd;j++) {if(!cnt[j])id[j]=tot++;}
	tot--;
	for(int i=0;i<=tot;i++) bs[i+tot+1][i]=1;
	len=tot*2+2;
	for(int j=0;j<=nd;j++) {
		if(cnt[j])continue;
		for(int i=1;i<=n;i++) {
			int u=j;bool f=1;
			for(int d=1;d<=sz[i];d++) {
				int x=s[i][d]-'a';
				u=go[u][x];
				if(cnt[u]||!u){f=0;break;}
			}
			if(!f) continue;
			int x=id[j],y=id[u];
			if(sz[i]==1) {to1[j][u]=1;bs[x+tot+1][y+tot+1]=1;}
			else {bs[x][y+tot+1]=1;}
		}
	}
}
int st[N];
void solve() {
	ksm(L);
	for(int i=0;i<=nd;i++) if(to1[0][i])st[id[i]+tot+1]=1;
	ll res=0;
	for(int j=0;j<=tot;j++) {
		ll tmp=ans[0][j];
		for(int i=tot+1;i<=tot*2+1;i++) if(st[i])tmp=(tmp+ans[i][j])%mod;
		res=(res+tmp)%mod;
	}
	res=(res+mod)%mod;
	printf("%lld",res);
}
int main() {
	scanf("%d%d%d",&n,&m,&L);
	for(int i=1;i<=n;i++) {scanf("%s",s[i]+1);Insert(i,0);}
	for(int i=1;i<=m;i++) {scanf("%s",s[i+n]+1);Insert(i+n,1);}
	gt_fail();
	if(L<=100)DP();
	else {
		init();solve();
	}
	return 0;
}

小结

首先是tle90分
我直接按上面写的,没有任何优化。
我想了一个办法能减大概\(/frac{1}{3}\)的时间,就是矩阵快速幂运算中去除cnt[]>0即含咒语不可走的点,我还调了很久QaQ。然后luogu上过了,NKOJ同样的数据却TLE了…
接着,我发现同学都跑的好快啊,是不是看了题解学了秘诀呢?于是我打开题解,就改了一下我的矩阵快速幂。(理论上能优化\(/frac{1}{2}\)的常数)实际上却更多。

ll ans[M][M],t[M][M];
void Mul() {
	for(int i=0;i<len;i++) for(int j=0;j<len;j++)t[i][j]=ans[i][j],ans[i][j]=0;
	for(int i=0;i<len;i++) for(int j=0;j<len;j++) {
		if(!t[i][j])continue;    //这里在带很多零的矩阵下非常适用
		for(int k=0;k<len;k++) ans[i][k]=(ans[i][k]+t[i][j]*bs[j][k])%mod;
	}
}

然后加上两个优化+O2就跑到了luogu第二:

标签:int,优化,void,魔法,矩阵,BJOI2017,咒语,快速,dp
来源: https://www.cnblogs.com/bestime/p/16023266.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有