您的位置:首页 > 其它

bzoj1009 [HNOI2008]GT考试

2015-06-20 20:39 399 查看

1009: [HNOI2008]GT考试

Time Limit: 1 Sec Memory Limit: 162 MB
Submit: 2120 Solved: 1304

Description

阿申准备报名参加GT考试,准考证号为N位数X1X2....Xn(0<=Xi<=9),他不希望准考证号上出现不吉利的数字。他的不吉利数学A1A2...Am(0<=Ai<=9)有M位,不出现是指X1X2...Xn中没有恰好一段等于A1A2...Am. A1和X1可以为0

Input

第一行输入N,M,K.接下来一行输入M位的数。 100%数据N<=10^9,M<=20,K<=1000 40%数据N<=1000 10%数据N<=6

Output

阿申想知道不出现不吉利数字的号码有多少种,输出模K取余的结果.

Sample Input

4 3 100

111

Sample Output

81

HINT

Source

大意:求有多少n位的字符串 不包含给出的字符串

分析:n那么大,那肯定是矩阵乘法快速幂了。

可以用KMP,但自从AC自动机出现,KMP就成了时代的眼泪(#题外话)

所以我不会用KMP,所以我用了AC自动机

其实无论是一个限制串还是多个限制串(前提是总字符数量不要太多),原理都是一样的

下面介绍一下AC自动机

(我的介绍比较简略,如果看还是结合别人的介绍比较好)

1 2 3 4 5 6 7 8 9 10 11

原始串 A : a b c d a b c d a b d

要寻找的串 B :a b c d a b d

比如我们现在匹配,比如第 7 位不同,我们肯定不会从B串的第一位从新开始寻找,而是从第3位开始匹配,因为第 1 - 2 个字符与第5 - 6个字符相同(都是a b ),所以与KMP类似,我们在建设AC自动机时,要对每一位(假设第 x 位)的字符都找一个点,使得第 1 - i 位的字符与 第 x - i + 1 - x位的字符相同,并是的这样的与开头一段相同的串最长(即 i 最大),这样来解决匹配失败的问题,这个东西我们用Next 来记录(即匹配失败后,从哪一位开始匹配),而AC自动机就是在字母树的基础上添加Next的用法,如图所示。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <deque>
#include <vector>
#include <queue>
#include <iostream>
#include <algorithm>
#include <map>
#include <set>
#include <ctime>
using namespace std;
typedef long long LL;
typedef double DB;
#define For(i, s, t) for(int i = (s); i <= (t); i++)
#define Ford(i, s, t) for(int i = (s); i >= (t); i--)
#define MIT (2147483647)
#define INF (1000000001)
#define MLL (1000000000000000001LL)
#define sz(x) ((int) (x).size())
#define clr(x, y) memset(x, y, sizeof(x))
#define puf push_front
#define pub push_back
#define pof pop_front
#define pob pop_back
#define ft first
#define sd second
#define mk make_pair
inline void SetIO(string Name) {
string Input = Name+".in",
Output = Name+".out";
freopen(Input.c_str(), "r", stdin),
freopen(Output.c_str(), "w", stdout);
}

const int N = 30, M = 20;
int n, m, Mod;
int Tot;
struct Matrix {
int M

;

Matrix() {
clr(M, 0);
}

inline Matrix operator *(Matrix &A) {
Matrix Ret;
For(i, 0, Tot)
For(j, 0, Tot)
For(k, 0, Tot) {
Ret.M[i][j] += M[i][k]*A.M[k][j];
Ret.M[i][j] %= Mod;
}
return Ret;
}
} Transfer;
struct Node {
int Child[M], Next;
bool Danger;
#define Child(x, y) Tr[x].Child[y]
#define Next(x) Tr[x].Next
#define Danger(x) Tr[x].Danger
} Tr
;
string S;
queue<int> Que;

inline void Input() {
scanf("%d%d%d", &n, &m, &Mod);
cin>>S;
}

inline int Find(int x, int y) {
while(x && !Child(x, y)) x = Next(x);
return Child(x, y);
}

inline void Build() {
int Now = 0;
For(i, 0, m-1) {
int x = S[i]-'0';
Child(Now, x) = ++Tot;
Danger(Tot) = 0;
Now = Tot;
}
Danger(Tot) = 1;

Next(0) = 0;
For(i, 0, 9) Next(Child(0, i)) = 0;
For(i, 0, 9)
if(Child(0, i)) Que.push(Child(0, i));
while(sz(Que)) {
int x = Que.front();
Que.pop();
for(int Tab = Next(x); Tab && !Danger(x); Tab = Next(Tab))
Danger(x) |= Danger(Tab);

For(i, 0, 9)
if(Child(x, i)) Que.push(Child(x, i));

For(i, 0, 9)
if(Child(x, i)) Next(Child(x, i)) = Find(Next(x), i);
else Child(x, i) = Find(Next(x), i);
}

For(i, 0, Tot) {
if(Danger(i)) continue;
For(j, 0, 9) {
if(Danger(Child(i, j))) continue;
Transfer.M[i][Child(i, j)]++;
}
}
}

inline Matrix Power(Matrix &Basic, int Tim) {
Matrix Ret;
For(i, 0, Tot) Ret.M[i][i] = 1;
while(Tim) {
if(Tim&1) Ret = Ret*Basic;
Basic = Basic*Basic, Tim >>= 1;
}
return Ret;
}

inline void Solve() {
Build();

Matrix Ret;
Ret = Power(Transfer, n);

int Ans = 0;
For(i, 0, Tot)
if(!Danger(i)) Ans += Ret.M[0][i];
Ans %= Mod;

printf("%d\n", Ans);
}

int main() {
SetIO("1009");
Input();
Solve();
return 0;
}


View Code

题外话:

为什么说KMP是时代的眼泪了呢?

其实这样说是不太准确的,KMP还是可以求最长重复子串的(子串之间不允许重叠),其他问题可以用其他方法解决

如果求最长重复子串(子串之间允许重叠),则用后缀数组,

至于其他,则大多可以用AC自动机代替

(当然,还有少数功能这里没有提到)

而后缀数组、AC自动机属于必学,且变化形式较少,代码较模版化,思考较简单,

KMP则有时需要大神的思维以及一点点小火花,

所以KMP还是不用专精,略学即可

(其实AC自动机跟KMP原理是一样的,我只不过实在胡说八道罢了)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: