bzoj1559/洛谷P4045 [JSOI2009]密码 AC自动机+状压DP+搜索

16 篇文章 0 订阅
6 篇文章 0 订阅

我差点没死在这道题上…

首先建出AC自动机,然后在每一个字符串的末尾节点用二进制状态记录该字符串已经完整地出现过了,然后设 f ( i , x , z t ) f(i,x,zt) f(i,x,zt)表示长度为 i i i的密码,对应AC自动机上的 x x x节点,当前每个串有没有出现的状态为 z t zt zt的方案数即可DP。

由于最终结果小于 2 63 2^{63} 263,所以我们可以DP的过程中对 2 64 2^{64} 264取模也就是用unsigned long long 自然溢出。

接下来就是解决方案的问题了。假如密码串中有一个不属于任何规定子串的字符,则该字符有26种情况,可以摆在一个字符的前后又有至少2种情况, 2 ∗ 26 = 52 > 42 2*26=52>42 226=52>42,所以密码串中不会有自由字符。假如我们枚举出一个各个子串出现的顺序,则一定要子串们尽可能“首位相融”。因为如果有一个没有达到完全“首位相融”的密码串满足条件,则完全“首位相融”后剩下的地方就可以放自由串。

我一开始枚举全排列然后再构造串,但那样太慢了,加了哈希依然不能过。这是由于从当前排列到下一排列,其实变动的元素很少,但是需要重新构造一次太浪费状态,所以dfs即可。

当然还有一点,如果一个给定子串是另一个给定子串的子串(呃…这个表述…),就应该删掉它,否则在搜方案上会出偏差。但是我没这么干也过了,就懒得加了,以下是HACK我自己的数据:

4 2
abba
bb
#include<bits/stdc++.h>
using namespace std;
#define RI register int
typedef unsigned long long LL;
const LL bas=131;
int L,n,SZ,js;string s[12],gg[36285],now;
int a[105][26],val[105],bin[12],fail[105],q[105],vis[12];
LL f[27][105][1030],ans,Bas[37],has[37],H[12][12];
void ins(int x) {
	int now=1;
	for(RI i=0;i<s[x].length();++i) {
		int t=s[x][i]-'a';
		if(!a[now][t]) a[now][t]=++SZ;
		now=a[now][t]; 
	}
	val[now]|=bin[x-1];
}
void getfail() {
	int he=1,ta=1;q[1]=1;
	while(he<=ta) {
		int x=q[he];++he;
		val[x]|=val[fail[x]];
		for(RI i=0;i<26;++i)
			if(a[x][i]) fail[a[x][i]]=a[fail[x]][i],q[++ta]=a[x][i];
			else a[x][i]=a[fail[x]][i];
	}
}
void dfs(int x) {
	if(now.length()>L) return;
	if(x==n+1) {gg[++js]=now;return;}
	string kl=now;
	for(RI i=1;i<=n;++i) {
		if(vis[i]) continue;
		int sL=s[i].length(),nL=now.length();
		for(RI j=min(nL,sL);j>=0;--j) {
			if(has[nL]-has[nL-j]*Bas[j]!=H[i][j]) continue;
			for(RI k=j;k<sL;++k) {
				now=now+s[i][k],++nL;
				has[nL]=has[nL-1]*bas+now[nL-1]-'a';
			}
			break;
		}
		vis[i]=1,dfs(x+1),now=kl,vis[i]=0;
	}
}
void QAQ() {
	for(RI i=1;i<=n;++i)
		for(RI j=0;j<s[i].length();++j)
			H[i][j+1]=H[i][j]*bas+s[i][j]-'a';
	Bas[0]=1;for(RI i=1;i<=L;++i) Bas[i]=Bas[i-1]*bas;
	dfs(1),sort(gg+1,gg+1+js);
	for(RI i=1;i<=js;++i)
		if(gg[i]!=gg[i-1]) cout<<gg[i]<<endl;
}
int main()
{
	scanf("%d%d",&L,&n);
	SZ=1;for(RI i=0;i<26;++i) a[0][i]=1;
	bin[0]=1;for(RI i=1;i<=n;++i) bin[i]=bin[i-1]<<1;
	for(RI i=1;i<=n;++i) cin>>s[i],ins(i);
	getfail(),f[0][1][0]=1;
	for(RI i=0;i<L;++i)
		for(RI x=1;x<=SZ;++x)
			for(RI zt=0;zt<bin[n];++zt) {
				if(!f[i][x][zt]) continue;
				for(RI j=0;j<26;++j)
					f[i+1][a[x][j]][zt|val[a[x][j]]]+=f[i][x][zt];
			}
	for(RI x=1;x<=SZ;++x) ans+=f[L][x][bin[n]-1];
	printf("%llu\n",ans);
	if(ans<=42) QAQ();
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值