题目链接:http://poj.org/problem?id=1625
RE:爆char
WA:还是爆char
解题思路:
思路和POJ2778是一样的:https://blog.csdn.net/weixin_43768644/article/details/99050540
但是这题矩阵快速幂会T
设字符种类为M,串长度为L,病毒串总节点数N
矩阵快速幂复杂度O(N^3*logL),适用于L比N大得多
DP复杂度为O(L*N*M),适用于L和N差不多的情况
dp[i][j]表示长度为i起点为0,终点为j(字典树上的节点)的方案数
dp[i][j] += dp[i-1][k] (k可达j)
最后dp[L][0~N-1]的和就是答案,具体请看代码,这个应该不会很难理解。
大数直接拉板子的,这个板子不错,可以直接拿去用。
对了,串中不会有空格(ASCII>32),所以不用挖空心思想着怎么读入单个字符
代码:(又臭又长不过都是板子,很结构化)
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#include<set>
#include<map>
#include<algorithm>
using namespace std;
#define ll long long
#define ull unsigned long long
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
#define rof1(i,a,b) for (int i=a;i>=b;i--)
#define rof0(i,a,b) for (int i=a;i>b;i--)
#define pb push_back
#define fi first
#define se second
#define debug(x) printf("----Line %s----\n",#x)
#define pt(x,y) printf("%s = %d\n",#x,y)
#define INF 0x3f3f3f3f
#define dfl(x) ll x;scanf("%I64d",&x)
#define df2l(x,y) ll x,y;scanf("%I64d %I64d",&x,&y)
#define df(x) int x;scanf("%d",&x);
#define df2(x,y) int x,y;scanf("%d %d",&x,&y)
#define mod 1000000007
#define duozu(T) int T;scanf("%d",&T);while (T--)
const int N = 1e5+5;
const int maxnode = 100+5;//模式串数量*长度
const int ALP = 50;//字符种类数
int id[300+5];
unsigned char s[100];
int m,l,n;
///***大数模板***///
const int power = 8;///每次计算多少位
const int base = 100000000;///10^power
const int MAXL = 20;///200*power = 数据最多的位数
char a[MAXL], b[MAXL];
struct num
{
ll a[MAXL];
num() { memset(a, 0, sizeof(a)); }//初始化
num(char *s)//将一个字符串初始化为高精度数
{
memset(a, 0, sizeof(a));
int len = strlen(s);
a[0] = (len+power-1) / power;//用a[0]记数的 长度/几位存一个
for (ll i=0, t=0, w; i < len ;w *= 10, ++i)///w应该是当前的位,证明从低位赋值?
{
if (i % power == 0) { w = 1, ++t; }///每隔power位,w重新回归1,把这么多位存在一个一个里面
a[t] += w * (s[i]-'0');
}
}
void add(int k) { if (k || a[0]) a[ ++a[0] ] = k; } ///在末尾添加一个数,除法的时候要用到,真的是末尾添加一个数不是+1
void re() { reverse(a+1, a+a[0]+1); } ///把数反过来,除法的时候要用到
void print() ///打印此高精度数
{
printf("%d", a[ a[0] ]);
///先打印最高位,为了压位 或者 该高精度数为0 考虑
for (int i = a[0]-1;i > 0;--i)
printf("%0*d", power, a[i]);
///这里"%0*d", power的意思是,必须输出power位,不够则前面用0补足
//printf("\n");
}
}dp[105][105];
num add1("1");
bool operator < (const num &p, const num &q) ///判断小于关系,除法的时候有用
{
if (p.a[0] < q.a[0]) return true;
if (p.a[0] > q.a[0]) return false;
for (int i = p.a[0];i > 0;--i)
{
if (p.a[i] != q.a[i]) return p.a[i] < q.a[i];
}
return false;
}
num operator + (const num &p, const num &q) ///加法,不用多说了吧,模拟一遍,很容易懂
{
num c;
c.a[0] = max(p.a[0], q.a[0]);
for (int i = 1;i <= c.a[0];++i)
{
c.a[i] += p.a[i] + q.a[i];
c.a[i+1] += c.a[i] / base;
c.a[i] %= base;
}
if (c.a[ c.a[0]+1 ]) ++c.a[0];
return c;
}
num operator - (const num &p, const num &q) ///减法,也不用多说,模拟一遍,很容易懂
{
num c = p;
for (int i = 1;i <= c.a[0];++i)
{
c.a[i] -= q.a[i];
if (c.a[i] < 0) { c.a[i] += base; --c.a[i+1]; }
}
while (c.a[0] > 0 && !c.a[ c.a[0] ]) --c.a[0];
///我的习惯是如果该数为0,那么他的长度也是0,方便比较大小和在末尾添加数时的判断。
return c;
}
num operator * (const num &p, const num &q)
///乘法,还是模拟一遍。。其实高精度就是模拟人工四则运算!
{
num c;
c.a[0] = p.a[0]+q.a[0]-1;
for (int i = 1;i <= p.a[0];++i)
for (int j = 1;j <= q.a[0];++j)
{
c.a[i+j-1] += p.a[i]*q.a[j];
c.a[i+j] += c.a[i+j-1] / base;
c.a[i+j-1] %= base;
}
if (c.a[ c.a[0]+1 ]) ++c.a[0];
return c;
}
num operator / (const num &p, const num &q) ///除法,这里我稍微讲解一下
{
num x, y;
for (int i = p.a[0];i >= 1;--i) ///从最高位开始取数
{
y.add(p.a[i]); ///把数添到末尾(最低位),这时候是高位在前,低位在后
y.re(); ///把数反过来,变为统一的存储方式:低位在前,高位在后
while ( !(y < q) ) ///大于等于除数的时候,如果小于的话,其实答案上的该位就是初始的“0”
y = y - q, ++x.a[i]; ///看能减几个除数,减几次,答案上该位就加几次。
y.re(); ///将数反过来,为下一次添数做准备
}
x.a[0] = p.a[0];
while (x.a[0] > 0 && !x.a[x.a[0]]) --x.a[0];
return x;
}
///***大数模板***///
struct AC_am
{
queue<int>que;
int sz;
int trie[maxnode][ALP];
int fail[maxnode];
int last[maxnode];
int val[maxnode];//储存当前节点信息,如是否为单词节点等等
int newnode(int x){
memset(trie[x],0,sizeof trie[x]);
val[x] = 0;
return sz++;
}
void init(){
newnode(sz = 0);
}
int idx(unsigned char ch){//实际字符串转化为字典树对应节点,根据题目做出具体改变
return id[ch];
}
void insert(unsigned char *s){
int u = 0;
for (int i=0;s[i];i++){
int c = idx(s[i]);
if (!trie[u][c]){
trie[u][c] = newnode(sz);
}
u = trie[u][c];
}
val[u]++;
}
void build(){
fail[0] = 0;
for (int c=0;c<m;c++){
int v = trie[0][c];
if (v){
que.push(v);
fail[v] = 0;
last[v] = 0;
}
}
while (!que.empty()){
int u = que.front();que.pop();
for (int c=0;c<m;c++){
int v = trie[u][c];
if (!v){
trie[u][c] = trie[fail[u]][c];
v = trie[u][c];
continue;
}
int fa = fail[u];
fail[v] = trie[fa][c];
last[v] = val[fail[v]]? fail[v]:last[fail[v]];
que.push(v);
}
}
}
}ac;
int main()
{
//freopen("C:/Users/DELL/Desktop/input.txt", "r", stdin);
//freopen("C:/Users/DELL/Desktop/output1.txt", "w", stdout);
while (~scanf("%d %d %d",&m,&l,&n)){
memset(dp,0,sizeof dp);
ac.init();
cin>>s;
for0(i,0,m) id[(int)s[i]] = i;
for0(i,0,n){
cin>>s;
ac.insert(s);
}
ac.build();
dp[0][0] = add1;///表示长度为i,从0出发到j有几种方法
for1(i,1,l){///枚举长度
for0(j,0,ac.sz){///枚举起点
for0(k,0,m){///枚举子节点
int v = ac.trie[j][k];
if (!ac.val[v] && !ac.last[v])
dp[i][v] = dp[i][v] + dp[i-1][j];
}
}
}
num ans;
for0(i,0,ac.sz) ans = ans + dp[l][i];
ans.print();printf("\n");
}
return 0;
}