Zoj 3494 BCD Code (字符串_AC自动机(数位DP))

题目链接: http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=4317


题目大意: 问A到B之间的所有整数,转换成BCD Code后,有多少个不包含属于给定病毒串集合的子串,A,B <=10^200,病毒串总长度<= 2000.


解题思路: 因为时间问题,这是我这次复习AC自动机的最后一题了。ac自动机A的很爽,可是其他方面太弱逼,无可奈何必须去复习其他方面了。

    看完题目就给出题者跪了,看似毫不相关但实则有联系的的两个东西被结合起来,真是神来之笔。AC自动机上明显的状态使得我们很容易在AC自动机上进行DP,而数位DP又是DP里面一类比较典型的DP,之前我一直认为数位DP应该是利用数字直接一位一位的构造就可以,这题利用自动机构造出一个数字与其他数字间能否相互到达的矩阵,然后利用这个矩阵进行数位DP。这样一分析,似乎并不是毫无相关。

    以终为始,我们先分析下最后的数位dp为什么需要用到自动机。如果一个数不含病毒串,那么这个数中的所有数字编成BCD码并且连接起来后就必须不包含病毒串。普通的数位DP构造的两个相邻数字没什么约束,而这题的约束有两个,一是数字本身编成BCD码后可能含有病毒串,二是两个数字连起来后他们的BCD码也连起来了,这时可能含有病毒串,一是二的特例。这样问题就转变成了我们构造若干个串,使得这些串不包含病毒串。问题一转变就和AC自动机扯上关系了,我们用这个串去ac自动机上跑一跑就知道含不含病毒串了。

    我们需要将自动机上的状态压缩成一个mat[MAXSIZE][10]矩阵,mat[i][j]表示位置i走过j编成的BCD码即四位二进制数后到达的位置,如果没办法到达设为-1.接着我们利用mat进行数位DP,mat[i][j]中的i表示位置,但除root外它也表示数字,i->next[k] = ii,这里的ii就表示k

    用dp[pos][ac][limit][zero]来表示状态,pos表示数字的第几位,ac为ac自动机上的位置,limit=0表示没有上限,limit=1表示上限为digit[pos],zero为0表示非前导0,此时需要判断mat[ac][k]是否为-1,为-1说明没办法构造下一个数字k,否则可以构造,当zero为1表示是前导0,如果下一个是0那么表示这些都是无用的0,转移到下一个位置都应该是root,不是0的话就需要判断mat[0][k]了

    状态明确了以后用记忆化搜索很好写。


测试数据:

Input:
10
0
1 10
1
00
1 10
1
00
1 100
1
1111
1 100
2
01
10

1 100


Output:
10
3
9
98
0


代码:
#include <stdio.h>
#include <string.h>
#include <queue>
#include <algorithm>
using namespace std;
#define MAXNODE 2
#define MAXSIZE 2100
#define MOD 1000000009


int n,m;
int ans;
char str[MAXSIZE];
char A[MAXSIZE],B[MAXSIZE];


struct ACnode {

	int flag,in;
	ACnode *next[MAXNODE],*fail,*father;
};
struct AC_Auto {

	int head,tail,total;
	int digit[210];
	int ans,dp[210][MAXSIZE][2][2];
	int next[MAXSIZE][10];
	ACnode *root,*p,*q;
	ACnode *qu[MAXSIZE],tree[MAXSIZE];


	void Initial() {

		total = 0;
		head = tail = 0;
		root = CreateNode();
		root->fail = root;
	}
	ACnode *CreateNode() {

		tree[total].flag = 0;
		tree[total].in = total;
		tree[total].next[0] = NULL;
		tree[total].next[1] = NULL;
		return &tree[total++];
	}
	int GetHash(char c) {

		return c - '0';
	}
	void Insert(char *str) {

		p = root;
		for (int i = 0; str[i]; ++i) {

			int k = GetHash(str[i]);
			if (p->next[k] == NULL)
				p->next[k] = CreateNode();
			p = p->next[k];
		}
		p->flag = 1;
	}
	void Build_AC() {

		qu[head++] = root;
		while (tail < head) {

			p = qu[tail++];
			for (int k = 0; k < MAXNODE; ++k) 
				if (p->next[k]) {

					if (p == root) p->next[k]->fail = root;
					else p->next[k]->fail = p->fail->next[k];
					p->next[k]->flag |= p->next[k]->fail->flag;
					qu[head++] = p->next[k];
				}
				else {

					if (p == root) p->next[k] = root;
					else p->next[k] = p->fail->next[k];
				}
		}
	}
	int Next(int pos,int num) {

		if (tree[pos].flag == 1) return -1;
		int realpos = pos;
		for (int i = 3; i >= 0; --i) {

			int k = ((num >> i) & 1);
			if (tree[realpos].next[k]->flag != 1)
				realpos = tree[realpos].next[k]->in;
			else return -1;
		}
		return realpos;
	}
	void Cal_Next() {

		for (int i = 0; i < total; ++i)
			for (int j = 0; j <= 9; ++j)
				next[i][j] = Next(i,j);
	}
	int Dfs(int pos,int ac,int limit,int zero) {

		if (pos == -1) return 1;
		if (dp[pos][ac][limit][zero] != -1)
			return dp[pos][ac][limit][zero];


		int sum = 0;
		int tl,tz,end = limit ? digit[pos] : 9; 
		for (int i = 0; i <= end; ++i)  {

			tz = zero && i == 0;
			tl = limit && (i == end);
			

			if (tz == 1) {
				
				sum = sum + Dfs(pos-1,0,tl,tz);
				if (sum >= MOD) sum -= MOD;
				continue;
			}
			if (next[ac][i] == -1) continue;
			sum = sum + Dfs(pos-1,next[ac][i],tl,tz);
			if (sum >= MOD) sum -= MOD;
		}


		dp[pos][ac][limit][zero] = sum;
		return dp[pos][ac][limit][zero];
	}
	int Cal(char *num) {

		int pos;
		memset(dp,-1,sizeof(dp));
		reverse(num,num+strlen(num));
		for (pos = 0; num[pos]; ++pos)
			digit[pos] = num[pos] - '0';
		

		return Dfs(pos-1,0,1,1);
	}
	void Debug() {

		for (int i = 0; i < total; ++i)
			for (int j = 0; j <= 9; ++j)
				printf("i = %d j = %d next = %d\n",i,j,next[i][j]);
	}
}AC;
void Subtoone(char *A) {

	int len = strlen(A),i = len -1;
	if (A[i] == 0) {

		A[i] = '9';
		while (A[i] == '0') i--;
	}
	A[i] = A[i] - '0' - 1 + '0';
}


int main() 
{
	int i,j,k,t;


	scanf("%d",&t);
	while (t--) {

		scanf("%d",&n);
		AC.Initial();
		for (i = 1; i <= n; ++i) {

			scanf("%s",str);
			AC.Insert(str);
		}


		ans = 0;
		AC.Build_AC();
		AC.Cal_Next();
		//AC.Debug();


		scanf("%s",A);
		Subtoone(A);
		ans = ans - AC.Cal(A);
		if (ans < 0) ans += MOD;
		scanf("%s",B);
		ans = ans + AC.Cal(B);
		if (ans >= MOD) ans -= MOD;
		printf("%d\n",ans);
	}
}


本文ZeroClock原创,但可以转载,因为我们是兄弟。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值