質問の意味:長さがnの4つのACGT文字が与えられた場合、長さnの4つの文字列のうち、パターン文字列を含まない4つの文字列だけがいくつあるかを尋ね、これらの4つの文字で構成されるm文字列を与えます
アイデア:nの数が2e9レベルであることを考えると、1つのO(n)でも解決できないため、通常のトラバーサルは不可能です。長さnの文字列、これはトライツリーのn個のノードによって(転送されて)取得された文字列であることがわかります。この文字列に以前に指定したパターン文字列が含まれていない場合、これは以前に使用したものと同等です。パターン文字列の終了ノードを通過せずにルートノードからトライを作成する方法はいくつかあります。
離散数学では、グラフ内のある点iから別の点jへの到達可能な関係を見つけるには、グラフの隣接する行列でnを累乗するだけでよいという結論があります。iからjまでのステップ数を取得できます。メソッドの種類。したがって、必要なのは、トライ番号の隣接行列を見つけ、それを使用してすばやく解くだけです。
ps:ここで、ビルド関数では、pの各ノードループが完了した後、現在のpノードのフェイルポインターが指すノードの識別ステータスを確認する必要があります。フェイルポインターが指すノードが特定のパターン文字列の終了ノードである場合、次に、現在のポイントpを到達不能ポイントとして設定する必要があります。そうしないと、このポイントpがパターン文字列の終了ノードに渡されます。(つまり、end [p] | = end [fail [p]];)
ACコード:
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#include <cstdio>
#include <map>
using namespace std;
typedef long long ll;
const int MOD = 1e5;
const int MAX_TOT = 107;
const int MAXN = 17;
int m,n;
std::map<char,int>mp;//用一个map实现ACGT 到 0123的映射 这样next只需要开4个节点即可
//矩阵快速幂
struct Matrix
{
int mat[MAX_TOT][MAX_TOT],n;
Matrix(){
}
Matrix(int num){
n = num;
for(int i = 0;i < n;i ++){
for(int j = 0;j < n;j ++) mat[i][j] = 0;
}
}
};
Matrix mul(Matrix a,Matrix b)
{
Matrix tmp = Matrix(a.n);
for(int i = 0;i < a.n;i ++){
for(int j = 0;j < a.n;j ++){
for(int k = 0;k < a.n;k ++){
int sum = (ll)a.mat[i][k]*b.mat[k][j]%MOD;
tmp.mat[i][j] = (tmp.mat[i][j] + sum)%MOD;
}
}
}
return tmp;
}
Matrix quickpow(Matrix a,ll n)
{
Matrix E = Matrix(a.n);
for(int i = 0;i < a.n;i ++) E.mat[i][i] = 1;
while(n){
if(n&1) E = mul(E,a);
a = mul(a,a);
n >>= 1;
}
return E;
}
//
struct Aca
{
int Next[MAX_TOT][4],fail[MAX_TOT],end[MAX_TOT],size;
queue<int>q;
void init()
{
mp['A'] = 0,mp['C'] = 1,mp['T'] = 2,mp['G'] = 3;
while(!q.empty()) q.pop();
for(int i = 0;i < MAX_TOT;i ++){
end[i] = fail[i] = 0;
for(int ii = 0;ii < 4;ii ++) Next[i][ii] = 0;
}
size = 1;
}
void insert(char *s)
{
int len = strlen(s);
int p = 1;
for(int i = 0;i < len;i ++){
int c = mp[s[i]];
//printf("%d\n",c);
if(!Next[p][c]) Next[p][c] = ++size;
p = Next[p][c];
}
end[p] = 1;
}
void build()
{
for(int i = 0;i < 4;i ++) Next[0][i] = 1;
q.push(1);
fail[1] = 0;
while(!q.empty()){
int p = q.front();
q.pop();
for(int i = 0;i < 4;i ++){
int now = Next[p][i];
int fafail = fail[p];
if(!now){
Next[p][i] = Next[fafail][i];
continue;
}
fail[now] = Next[fafail][i];
q.push(now);
}
// 下一层不是结尾节点的点 如果他的fail指向了一个结尾节点 那么代表如果走到了这个节点失配
//的话 会走到fail处 因为fail是尾结点 所以当前失配位置也应当看做尾结点
end[p] |= end[fail[p]];//这有一个 end关系的传递要看到 wa吐了
}
}
Matrix getmartix()//获得trie树的临接矩阵
{
Matrix tmp = Matrix(size);
for(int i = 1;i <= size;i ++){
for(int j = 0;j < 4;j ++){
if(end[Next[i][j]]) continue;//这个点不可到达 因为他是尾结点
tmp.mat[i-1][Next[i][j]-1]++;//邻接矩阵可到达
}
}
return tmp;
}
}aca;
//输出矩阵检查用的
void opMatrix(Matrix a)
{
for(int i = 0;i < a.n;i ++){
for(int j = 0;j < a.n;j ++){
printf(j == a.n-1?"%d\n":"%d ",a.mat[i][j]);
}
}
}
void debug(){
for(int i = 1;i <= aca.size;i ++){
printf("---fail = %d\n",aca.fail[i]);
}
}
int main()
{
char s[MAXN];
aca.init();
scanf("%d%d",&m,&n);
for(int i = 1;i <= m;i ++){
scanf("%s",s);
aca.insert(s);
}
aca.build();
//debug();
Matrix res = aca.getmartix();
res = quickpow(res,n);
//printf("-------------\n");
//opMatrix(res);
//printf("-------------\n");
int ans = 0;
for(int i = 0;i < res.n;i ++){
ans = (ans + res.mat[0][i])%MOD;
}
printf("%d\n",ans);
return 0;
}