【JSOI2009】密码(AC自动机)(KMP)(状压DP)(爆搜)

传送门


下面的代码用了少量std::string的成员函数来完成部分骚操作,如果看不懂请自行百度。

题解:

这个数据范围和询问的问题就很状压。

直接建立AC自动机之后状压DP就可以算出方案数。

然后你发现,这是一个计数类问题,不是最优化,却要求输出方案,也就是说我们不太好记录DP的转移方向。

先骂一句MMP

但是只会在方案数小于等于 42 42 42的时候要求输出。

然后你发现直接在AC自动机上爆搜复杂度是 O ( 2 6 L ) O(26^L) O(26L),当场去世。

我们注意到小于等于42?也就是说不可能有任何位置是自由填字符串的,两个串的拼接也一定是最长公共前后缀拼接,不然方案数里面会乘上若干个26直接爆炸。

处理出两两之间的最长公共前后缀然后爆搜就行了,处理可以用KMP,把两个串拼接起来求个border。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

using std::string;

int n,L;
cs int N=13,mxL=26,SIZE=111;

string s[N];

int son[SIZE][26],fail[SIZE],cnt,st[SIZE];
inline void ins(cs string &s,int id){
	int len=s.size(),u=0;
	for(int re i=0;i<len;++i){
		int c=s[i]-'a';
		if(!son[u][c])son[u][c]=++cnt;
		u=son[u][c];
	}st[u]|=1<<id; 
}

int q[SIZE],qn;
inline void build_AC(){
	for(int re i=0;i<26;++i)if(son[0][i])q[++qn]=son[0][i];
	for(int re i=1;i<=qn;++i){
		int u=q[i];st[u]|=st[fail[u]];
		for(int re c=0;c<26;++c)
		son[u][c]?(fail[son[u][c]]=son[fail[u]][c],q[++qn]=son[u][c]):(son[u][c]=son[fail[u]][c]);
	}
}

inline void init(){
	std::sort(s,s+n,[](cs string &a,cs string &b){return a.size()>b.size();});
	int m=1;for(int re i=1;i<n;++i){
		bool flag=true;
		for(int re j=0;j<m&&flag;++j)flag&=s[j].find(s[i])==s[j].npos;
		if(flag)s[m++]=s[i];
	}n=m;
}

ll f[mxL][SIZE][1<<10|1];

inline int get_nxt(cs string &s,cs string &t){//suffix s = prefix t
	string a=(string)(" ")+t+s;
	static int nxt[40];int len=a.size()-1;
	for(int re i=2,j=0;i<=len;++i){
		while(j&&a[i]!=a[j+1])j=nxt[j];
		if(a[i]==a[j+1])++j;nxt[i]=j;
	}
	int tl=t.size(),res=nxt[len];
	while(res>tl)res=nxt[res];
	return res;
}

int nxt[N][N];
string S[50];
string tp;
void dfs(int l,int pre,int state){
	if(l==L&&state==(1<<n)-1){S[++cnt]=tp;return ;}
	for(int re i=0;i<n;++i)if(!(state&1<<i)){
		int st=pre==-1?0:nxt[pre][i];
		int nl=l-st+s[i].size();
		if(nl<=L){
			string tmp=tp;
			tp+=s[i].substr(st);
			assert(tp.size()==nl);
			dfs(nl,i,state|1<<i);
			tp=tmp;
		}
	}
}

signed main(){
#ifdef zxyoi
	freopen("password.in","r",stdin);
#endif
	std::ios::sync_with_stdio(false);
	std::cin>>L>>n;
	for(int re i=0;i<n;++i)std::cin>>s[i];init();
	for(int re i=0;i<n;++i)ins(s[i],i);build_AC();
	f[0][0][0]=1;int S=1<<n;
	for(int re i=0;i<L;++i)
	for(int re u=0;u<=cnt;++u)
	for(int re s=0;s<S;++s)if(f[i][u][s])
	for(int re c=0;c<26;++c)f[i+1][son[u][c]][s|st[son[u][c]]]+=f[i][u][s];
	ll ans=0;
	for(int re i=0;i<=cnt;++i)ans+=f[L][i][S-1];
	std::cout<<ans<<"\n";
	if(ans<=42){
		cnt=0;
		for(int re i=0;i<n;++i)
		for(int re j=0;j<n;++j)if(i!=j)nxt[i][j]=get_nxt(s[i],s[j]);
		dfs(0,-1,0);assert(cnt==ans);
		std::sort(::S+1,::S+cnt+1);
		for(int re i=1;i<=cnt;++i)std::cout<<::S[i]<<"\n";
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值