空罐 AC自动机 + DP 好题

链接:http://zerojudge.tw/ShowProblem?problemid=b179

做这道题首先要对ac自动机理解透彻!!

蛮好的一道题目,每一天字符串除了会在尾部扩展一个,开头的字符还会消失使得长度变短。最后要统计n天的时间内多少个串消失了,多少个串生病住院了(包含了病毒串)

要描述这个状态,需要考虑经过了几天  串的长度是多少  停留在哪个节点,一般自动机 dp都是这样,很好想,关键是怎么转移

首先在尾部扩展一个字符,这是一般自动机DP都有的转移

但是在头部删除一个字符就有点妙了

分三种情况

1:串的长度为1 时,删除后就消失了,这个时候加上当前状态

2:串的长度恰好为所停留在节点的深度,长度减一后无法停留在当前节点,那往哪里走呢?显然是fail指针!

3:串的长度大于这个节点的深度,那肯定还是长度减一,停留在当前节点。


长度小于当前节点的深度都是非法状态,不用考虑

还有个注意点就是第一维要用滚动数组,滚动时别忘记清空

错了一次,数组开小了。

一组数据,帮助调试

a
3
3
ab
cc
dd
4 14


#include<cstdio>
#include<cstring>
#include<map>
#include<vector>
#include<algorithm>
using namespace std;
const int M = 1510;
const int CD = 4;
int ID[128];;
int ch[M][CD];
int Q[M];
int fail[M];
int sz;
int val[M];
void Init(){
	sz=1;
	memset(ch[0],0,sizeof(ch[0]));
	fail[0]=0;  val[0]=0;
	ID['a']=0; ID['b']=1;ID['c']=2;ID['d']=3;
}
int dep[M];
void Insert(char *s) {
	int p=0; int d=0;
	for(;*s;s++) {
		d++;
		int c=ID[*s];
		if(!ch[p][c]) {
			memset(ch[sz],0,sizeof(ch[sz]));
			val[sz]=0;
			dep[sz]=d;
			ch[p][c]=sz++;
		}
		p=ch[p][c];
	}
	val[p]=1;
}
void Construct() {
	int *s=Q,*e=Q;
	for(int i=0;i<CD;i++) {
		if(ch[0][i]) {
			fail[ch[0][i]] = 0;
			*e++ = ch[0][i];
		}
	}
	while(s!=e) {
		int u = *s++;
		for(int i=0;i<CD;i++) {
			int &v = ch[u][i];
			if(v) {
				*e++ = v;
				fail[v] = ch[fail[u]][i];
				val[v] |= val[fail[v]];
			} else {
				v = ch[fail[u]][i];
			}
		}
	}
}
const int mod = 10007;
char str[110];
int dp[2][110][1510];
bool init(char *s)
{
	memset(dp,0,sizeof(dp));
	int p=0,len=0;
	for(;*s;s++) 
	{
		len++;
		int c=ID[*s];
		p=ch[p][c];
	}
	dp[0][len][p]=1;
	if(val[p])return false;
	return true;
}
void solve(int n)
{
	bool flag=init(str);
    if(!flag) {
		puts("0 1\n");return ;
	}
	int x=0,ans=0,ret=0;
	for(int i=0;i<n;i++)
	{
		x^=1;
		for(int y=1;y<=100;y++)	memset(dp[x][y],0,sizeof(dp[x][y]));
		for(int j=0;j<sz;j++)if(!val[j])
		{
			for(int len=1;len<=100;len++) if(dp[x^1][len][j])//串长度为len 停留在j节点
			{
				if(len == 1) {
					ans=(ans+dp[x^1][len][j])%mod;
				} else if(len==dep[j]) {
					dp[x][len-1][fail[j]] += dp[x^1][len][j];
					dp[x][len-1][fail[j]] %= mod;
				} else if(len>dep[j]){
					dp[x][len-1][j] += dp[x^1][len][j];
					dp[x][len-1][j] %= mod;
				}
				for(int k=0;k<4;k++) {
					int to=ch[j][k];
					dp[x][len+1][to] += dp[x^1][len][j];
					dp[x][len+1][to] %= mod;
					if(val[to]) {
						ret=(ret+dp[x^1][len][j])%mod;
					}
				}
			}
		}
	}
	printf("%d %d\n",ans,ret);
}
int main(){
	char s[110];
	Init();
	scanf("%s",str);
	int n,p;
	scanf("%d%d",&n,&p);
	for(int i=0;i<p;i++)
	{
		scanf("%s",s);
		Insert(s);
	}
	Construct();
	solve(n);
	return 0;
}
/*
a
3
3
ab
cc
dd

4 14
*/


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值