bzoj 5451: 字符串

37 篇文章 0 订阅
6 篇文章 0 订阅

题意:

给定正整数m以及n个01串s1~sn,你需要求出长度为2m的反对称的包含这n个01串作为子串的01串的个数。对998244353取模。
一个01串s是反对称的当且仅当它对于 1 &lt; = i &lt; = ∣ s ∣ 1&lt;=i&lt;=|s| 1<=i<=s都满足 s [ i ] ≠ s [ ∣ s ∣ − i + 1 ] s[i]≠s[|s|-i+1] s[i]̸=s[si+1]

题解:

先考虑不需要反回文怎么做
显然就是AC自动机+dp的经典操作, f [ i ] [ x ] [ s ] f[i][x][s] f[i][x][s]表示前 i i i位,到 x x x节点,当前子串出现状态为 s s s的方案数, a n s = ∑ x = 1 t o t f [ m ] [ x ] [ 2 n − 1 ] ans=\sum_{x=1}^{tot}f[m][x][2^n-1] ans=x=1totf[m][x][2n1]
那么现在只dp前半部分,首先将每个串的逆反串加入自动机,但是还有一个问题,就是跨过左右两部分的子串比较麻烦。
可以发现,只有子串的某些位置,才会出现这种情况,那么只要找这些位置,和自动机上对应的节点,标记下即可,那么就有 a n s = ∑ x = 1 t o t f [ m ] [ x ] [ s ] ( s &amp; d x = 2 n − 1 ) ans=\sum_{x=1}^{tot}f[m][x][s](s \&amp; d_x=2^n-1) ans=x=1totf[m][x][s](s&dx=2n1)其中d为这个点可以制造的跨两边的串的集合。
code:

#include<queue>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int mod=998244353;
struct trnode{
	int a[2],fail,c,d;
}tr[1210];int tot=1;
int n,m,pre[2][110];
char s[8][110];
void insert(int k)
{
	int len=strlen(s[k]+1),x=1;
	for(int i=1;i<=len;i++)
	{
		int c=s[k][i]-'0';
		if(!tr[x].a[c]) tr[x].a[c]=++tot;
		x=tr[x].a[c];
	}
	tr[x].c|=(1<<k);
}
queue<int> q;
void make_fail()
{
	q.push(1);
	while(!q.empty())
	{
		int x=q.front();q.pop();
		for(int i=0;i<2;i++) if(tr[x].a[i])
		{
			int y=tr[x].a[i];
			if(x==1) tr[y].fail=1;
			else
			{
				int j=tr[x].fail;
				while(!tr[j].a[i]) j=tr[j].fail;
				tr[y].fail=tr[j].a[i];
				tr[y].c|=tr[tr[j].a[i]].c;
				tr[y].d|=tr[tr[j].a[i]].d;
			}
			q.push(y);
		}
	}
	for(int x=1;x<=tot;x++)
		for(int i=0;i<2;i++)
		{
			int j=x;
			while(!tr[j].a[i]) j=tr[j].fail;
			tr[x].a[i]=tr[j].a[i];
		}
}
bool check(int k,int l,int n,int op)
{
	if(l*2<n) return false;
	if(l*2==n&&op) return false;
	for(int i=1;l+i<=n;i++)
		if(s[k][l-i+1]==s[k][l+i]) return false;
	return true;
}
int f[510][1210][1<<6];
void solve(int k)
{
	for(int x=1;x<=tot;x++)
		for(int s=0;s<1<<n;s++) if(f[k][x][s])
		{
			for(int c=0;c<2;c++)
			{
				int y=tr[x].a[c],t=s|tr[y].c;
				(f[k+1][y][t]+=f[k][x][s])%=mod;
			}
		}
}
int main()
{
	tr[1].a[0]=++tot;tr[1].a[1]=++tot;
	scanf("%d %d",&n,&m);
	for(int i=0;i<n;i++) scanf("%s",s[i]+1),insert(i);
	for(int i=0;i<n;i++)
	{
		int len=strlen(s[i]+1),x=1;
		for(int j=1;j<=len;j++)
		{
			x=tr[x].a[s[i][j]-'0'];
			if(check(i,j,len,0)) tr[x].d|=(1<<i);
		}
		reverse(s[i]+1,s[i]+len+1);
		for(int j=1;j<=len;j++) s[i][j]=(s[i][j]=='0')?'1':'0';
	}
	for(int i=0;i<n;i++) insert(i);
	for(int i=0;i<n;i++)
	{
		int len=strlen(s[i]+1),x=1;
		for(int j=1;j<=len;j++)
		{
			x=tr[x].a[s[i][j]-'0'];
			if(check(i,j,len,1)) tr[x].d|=(1<<i);
		}
	}
	make_fail();
	f[0][1][0]=1;
	for(int i=0;i<m;i++) {solve(i);}
	int ans=0;
	for(int x=1;x<=tot;x++)
		for(int s=0;s<1<<n;s++)
			if(f[m][x][s]) if((s|tr[x].d)==(1<<n)-1) (ans+=f[m][x][s])%=mod;
	printf("%d",ans);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值