题目链接: 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
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原创,但可以转载,因为我们是兄弟。