题目链接:http://poj.org/problem?id=1625
题目大意:给定n个不同的字符,用这些字符组成长度为m的字符串但是不能出现p个危险字符串中的任何一个,危险字符串也不可以作为子串,问方案数有几种。
解题思路:这题可以用AC自动机+DP+高精度解。先不管那些危险字符串,本题可理解为n叉树,从根节点开始到叶子节点共有m层,方案数为n^m种,但是有那些危险字符串,某些子树就不能计算在那。但这样做太暴力了,时间空间复杂度都太高。但我们可以向想,长度为m的方案肯定由长度为m-1的方案转移过来,以此类推,它并没有后效性,可用DP思想,可想到状态转移方程:dp[i+1][next] += dp[i][j];(dp[i][j]表示长度为i且在ac自动机的j位置的方案数,next为j的下一个合法位置)。
本题的解题步骤是:先用p个危险字符串建立一棵字典树,再改造成trie图(和普通的AC自动机处理fail指针的方式不同,更高效),然后在AC自动机上DP,由于最大的方案数为50^50 ,大概是86位,必须用高精度,也就是用模拟来实现加法。
本题有几个Trick:1、危险字符串中存在某些串是其他串子串的情况,所以在改造trie图时要加一句this->flag |= this->fail->flag。 2、最后的答案可能是0,由于高精度的题目容易无视0,所以要特别注意。 3、输入的字符只说Ascii码大于32,有可能含有空格,
测试数据:
1 1 1a
a
ACGT
TCT
C
abcdefghijk
a
ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqr
代码:
#include <stdio.h>
#include <string.h>
#define MIN 60
#define MAX 800
struct node {
int in,flag;
node *fail,*next[MIN];
} arr[MAX],*qu[MAX],*root;
int n,m,p,total,hash[MAX];
char dir[MIN],str[MIN];
int num[2][MAX],dp[2][MAX][100]; //dp滚动数组,i = 0表示前一次结果,i = 1表示后一次结果,num表示位数
node *CreateNode(){
node *p = &arr[total];
p->flag = 0,p->in = total++;
for (int i = 0; i < n; ++i)
p->next[i] = NULL;
return p;
}
void Insert(char *str) {
int i = 0,k;
node *p = root;
while (str[i]) {
k = hash[str[i++]];
if (p->next[k] == NULL)
p->next[k] = CreateNode();
p = p->next[k];
}
p->flag = 1;
}
void Build_AC() {
int head,tail,i;
head = tail = 0;
root->fail = root;
qu[++head] = root;
while (tail < head) {
node *p = qu[++tail];
p->flag |= p->fail->flag; //如果后缀节点为危险节点,前面已经求出
for (i = 0; i < n; ++i)
if (p->next[i] != NULL) {
if (p == root) p->next[i]->fail = root;
else p->next[i]->fail = p->fail->next[i];
qu[++head] = p->next[i];
}
else {
if (p == root) p->next[i] = root;
else p->next[i] = p->fail->next[i];
}
}
}
void BigNumAdd(int *dp1,int *dp0) {
//dp0加到dp1,n为dp1位数,m为dp0位数
for (int i = 0; i < 90; ++i) {
dp1[i] += dp0[i];
if (dp1[i] >= 10)
dp1[i] %= 10,dp1[i+1] += 1;
}
}
void Solve_1A() {
int i,j,k,ans;
memset(dp,0,sizeof(dp));
memset(num,0,sizeof(num));
dp[0][0][0] = num[0][0] = 1;
for (i = 0; i < m; ++i) {
for (j = 0; j < total; ++j)
for (k = 0; k < n; ++k) {
node *p = arr[j].next[k];
node *temp = p;
if (p->flag != 1)
BigNumAdd(dp[1][p->in],dp[0][j]);//dp[i+1][p->in] += dp[i][j];
}
memcpy(dp[0],dp[1],sizeof(dp[1]));
memset(dp[1],0,sizeof(dp[1]));
}
for (i = 0; i < total; ++i)
BigNumAdd(dp[1][0],dp[0][i]);
for (k = 0,i = 90; i >= 0;) {
if (dp[1][0][i]) {
k = 1;
while (i >= 0)
printf("%d",dp[1][0][i]),i--;
}
else i--,num[1][0]--;
}
if (k == 0) printf("0"); //结果为0的时候上面那些运算会无视它,必须特判
printf("\n");
}
int main()
{
int i,j,k;
while (scanf("%d%d%d",&n,&m,&p) != EOF) {
total = 0;
root = CreateNode();
memset(hash,0,sizeof(hash));
getchar();
gets(str);
for (i = 0; str[i]; ++i)
hash[str[i]] = i;
for (i = 0; i < p; ++i)
gets(dir),Insert(dir);
Build_AC();
Solve_1A(); //输出也在里面
}
return 0;
}
本文ZeroClock原创,但可以转载,因为我们是兄弟。