雅礼集训1.2 T3字符串

该文章描述了一个关于字符串处理的问题,要求找到长度为2^m的反对称01串中,包含给定n个01串作为子串的个数。解决方案包括四种情况分析,使用AC自动机进行动态规划,时间复杂度为O(2^n*n*|s_i|*m),其中s_i为每个输入字符串。最后给出了C++代码实现。

题目描述

给定一个正整数mmm以及nnn010101s1,s2,…,sns_1,s_2,\dots,s_ns1,s2,,sn,你需要求出长度为2m2m2m的反对称的包含这nnn010101串作为子串的010101串的个数,对998244353998244353998244353取模。

一个010101sss是反对称的当且仅当它对于1≤i≤∣s∣1\leq i\leq |s|1is,都满足s[i]≠s[∣s∣−i+1]s[i]\neq s[|s|-i+1]s[i]=s[si+1]

输入格式

第一行两个整数n,mn,mn,m,接下来nnn行每行一个字符串s1,s2,…,sns_1,s_2,\dots,s_ns1,s2,,sn

输出格式

一行一个整数表示答案模998244353998244353998244353后的值。

样例输入

2 3
011
001

样例输出

4

数据范围

1≤n≤6,1≤∣si∣≤100,1≤m≤5001\leq n\leq 6,1\leq |s_i|\leq100,1\leq m\leq 5001n6,1si100,1m500


题解

每个串有四种情况:在前一半出现,在后一半出现,跨越中间且前一半长度大,跨越中间且后一半长度大。

sss在后一半出现,也就是sss前后翻转,每位取反后的字符串在前一半出现,那么我们对每个串,都再记录一个翻转再取反后的串即可。

对于第三种情况,我们需要对字符串的所有超过一半的前缀判断如果以这个前缀为前半段的结尾,能否构造出这个字符串。对于第四种情况,将sss翻转再取反的操作按上述方法处理即可。

ACACAC自动机上做状压DP即可。可以根据代码帮助理解。

时间复杂度为O(2n×n×∣si∣×m)O(2^n\times n\times |s_i|\times m)O(2n×n×si×m)

注意
  • 空间压得比较死,所以数组要开小一点,因为指涉及加法,所以不需要开long long

code

#include<bits/stdc++.h>
using namespace std;
const int N=1200;
int n,m,l,tot=0,w[N+5],fn[N+5],t[N+5],fa[N+5],ch[N+5][2];
	//w表示这个点正串的结尾,fn表示这个点是反串的结尾,t表示fail指针
int ans=0,f[505][N+5][70];
int mod=998244353;
char s[505];
queue<int>g;
bool pd(int t){
	for(int i=1;t+i<l;i++){
		if(t-i+1<0||s[t-i+1]==s[i+t]) return 0;
	}
	return 1;
}
void pt(int k){
	int q=0;
	for(int i=0;i<l;i++){
		if(!ch[q][s[i]-'0']){
			ch[q][s[i]-'0']=++tot;
			fa[tot]=q;
		}
		q=ch[q][s[i]-'0'];
		if(pd(i)) fn[q]|=k;
	}
	w[q]|=k;
}
int gt(int u){
	return ch[fa[u]][1]==u;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%s",s);
		l=strlen(s);
		pt(1<<i-1);
		for(int j=0;j<l-j-1;j++) swap(s[j],s[l-j-1]);
		for(int j=0;j<l;j++) s[j]^=1;
		pt(1<<i-1);
	}
	g.push(0);
	while(!g.empty()){
		int u=g.front();g.pop();
		if(ch[u][0]) g.push(ch[u][0]);
		if(ch[u][1]) g.push(ch[u][1]);
		if(!u) continue;
		int v=t[fa[u]],q=gt(u);
		while(v&&!ch[v][q]) v=t[v];
		if(ch[v][q]&&ch[v][q]!=u) t[u]=ch[v][q];
		if(!ch[u][0]) ch[u][0]=ch[t[u]][0];
		if(!ch[u][1]) ch[u][1]=ch[t[u]][1];
		w[u]|=w[t[u]];
		fn[u]|=fn[t[u]];
	}
	f[0][0][0]=1;
	for(int i=1;i<=m;i++){
		for(int j=0;j<=tot;j++){
			for(int p=0;p<(1<<n);p++){
				int u=ch[j][0];
				f[i][u][p|w[u]]=(f[i][u][p|w[u]]+f[i-1][j][p])%mod;
				u=ch[j][1];
				f[i][u][p|w[u]]=(f[i][u][p|w[u]]+f[i-1][j][p])%mod;
			}
		}
	}
	for(int i=0;i<=tot;i++){
		for(int p=0;p<(1<<n);p++){
			if((p|fn[i])==(1<<n)-1){
				ans=(ans+f[m][i][p])%mod;
			}
		}
	}
	printf("%d",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值