計蒜客 疑似病毒 (AC自動機 + 可達矩陣)


鏈接 : Here!

背景 : 開始我同學是用 AC自動機 + DP 的方法來做這道題, 這道題的標籤是 AC自動機, 動態規劃, 矩陣, 按道理來說 AC自動機 + DP 應該是能過的, 但是他不幸的 $T$ 了, $QAQ$, 後來神犇Hug給我們提供了一個思路!!!

思路 : 題目要求是 "如果其中兩個或者兩個以上的 $DNA$ 序列是一個 $DNA$ 序列 $A$ 的子串,那麼 $DNA$ 序列 $A$ 是疑似病毒的 $DNA$ 序列", 那麼也就是說字符串 $A$, 它要是能在自動機中走到兩次終態, 那麼它就是疑似病毒的 $DNA$ 序列. 因爲AC自動機可以等價轉化爲一個圖, 所以說這個問題就變成了, 從 $root$ 節點出發, 在一個圖中 $l$ 步通過終態點兩次或者兩次以上的方法數....................!!! $GG$

神犇告訴我們, 可以一個自動機在邏輯層面抽象成三層自動機, 然後用搜索來初始化可達矩陣, 這個可達矩陣$Matrix[i][j] = cnt$ 代表從 $i$ 點經過 $1$ 步到達 $j$ 點的方法數爲 $cnt$. 然後將初始的可達矩陣求 $l$ 次方, 就可以得到一個增量矩陣 $Matrix'$ , 最後只需要用初始的矩陣 $[1, 0, 0, ....] * Matrix'$ 就可以得到答案了.

細節 :

  1. 在初始化可達矩陣的時候, 對於任意一個節點, 它都能接受 $A, T, C, G$ 四個字符!!!, (因爲這個地方wa了無數次).
  2. 搜索時從當前層究竟能調到第幾層的條件是自動機的節點的cal_value的所決定的.
  3. 注意建立自動機的時候需要注意, 靠下的終態點需要加上它所有 $fail$ 指針的base_value.

補充 : 這道題的確非常有價值, 能夠加深對AC自動機的理解, 而且還有將字符串匹配問題轉爲圖的問題, 進而用可達矩陣來表示連通性, 然後通過矩陣快速冪來求出 $l$ 長度的 $DNA$ 序列種類數.

最後 : 待補充吧... 看起來理解還是不太夠, 雖然聽着思路能A, 但是找到方向纔是更重要的事情!!!


/*************************************************************************
    > File Name: ac_machine.cpp
    > Author: 
    > Mail: 
    > Created Time: 2017年11月25日 星期六 11時01分11秒
 ************************************************************************/

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;

const int MAX_N = 10000;
const int MAT_X = 200;
const int SIZE = 4;
const int BASE = '0'; 
const int LAYER = 3;
const int MOD = 10007;
int node_cnt = 1;  // 統計節點數量用於分配index, 在insert中統計, 在automaton中分配
int mat_size;

// Matrix 結構聲明
struct Matrix {
    Matrix() {
        memset(this->m, 0, sizeof(this->m));
    }
    int m[MAT_X][MAT_X];
    void show() {
        for (int i = 0 ; i < mat_size ; ++i) {
            for (int j = 0 ; j < mat_size ; ++j) {
                printf("%4d ", this->m[i][j]);
            }
            printf("\n");
        }
    }
};
Matrix unit_mat;
Matrix CGMatrix; // 可達矩陣

void init_unint_mat() {
    for (int i = 0 ; i < MAT_X ; ++i) {
        unit_mat.m[i][i] = 1;
    }
}

Matrix operator* (const Matrix &a, const Matrix &b) {
    Matrix c;
    for (int i = 0 ; i < mat_size ; ++i) {
        for (int j = 0 ; j < mat_size ; ++j) {
            int sum = 0;
            for (int k = 0 ; k < mat_size ; ++k) {
                sum += (a.m[i][k] * b.m[k][j]);
                sum %= MOD;
            }
            c.m[i][j] = sum % MOD;
        }
    }
    return c;
}

// 快速冪 : 計算矩陣a的x次方
Matrix quick_mat_pow(Matrix a, int x) {
    Matrix ret = unit_mat;
    while (x) {
        // TODO
        if (x & 1) ret = ret * a;
        a = a * a;
        x >>= 1;
    }
    return ret;
}

// Trie 結構聲明
// base_val : 基礎值
// cal_val  : 計算值
// index    : 下標
typedef struct Trie {
    int base_val;
    int cal_val[LAYER];
    int index[LAYER];
    struct Trie *fail;
    struct Trie **childs;
} Node, *Tree;

Trie* new_node() {
    Trie *p = (Trie *)malloc(sizeof(Trie));
    p->childs = (Trie **)malloc(sizeof(Trie *) * SIZE);
    memset(p->childs, 0, sizeof(Trie *) * SIZE);
    p->fail = NULL;
    p->base_val = 0;
    memset(p->index, 0, sizeof(int) * LAYER);
    memset(p->cal_val, 0, sizeof(int) * LAYER);
    return p;
}

void clear(Trie *node) {
    if (node == NULL) return;
    for (int i = 0 ; i < SIZE ; ++i) {
        clear(node->childs[i]);
    }
    free(node->childs);
    free(node);
}

void insert(Trie *node, char *str) {
    Trie *p = node;
    for (int i = 0 ; str[i] ; ++i) {
        if (p->childs[str[i] - BASE] == NULL) {
            p->childs[str[i] - BASE] = new_node();
            // 更新節點數量
            ++node_cnt;
        }
        p = p->childs[str[i] - BASE];
    }
    p->base_val = 1;
}

// 建立自動機
void build_automaton(Trie *root) {
    root->fail = NULL;
    Trie *que[MAX_N];
    int l = 0, r = 0, k = 0; // k用於計算index
    que[r++] = root;
    while (l < r) {
        Trie *now = que[l++];
        
        // 更新三層下標
        now->index[0] = k;
        now->index[1] = k + node_cnt;
        now->index[2] = k + node_cnt * 2;
        ++k;
        
        for (int i = 0 ; i < SIZE ; ++i) {
            if (now->childs[i] == NULL) continue;
            Trie *child = now->fail;
            while (child && child->childs[i] == NULL) {
                child = child->fail;
            }
            if (child == NULL) {
                child = root;
            } else {
                child = child->childs[i];
            }
            now->childs[i]->fail = child;
            now->childs[i]->base_val += now->childs[i]->fail->base_val;
            que[r++] = now->childs[i];
        }
    }
}

// 判斷在第幾個自動機
int inLayer(int x) {
    return (x <= 2 ? x : 2);
}

// 得到孩子的下標
int getChildIndex(Trie *now, Trie *child, int now_ind) {
    return child->index[inLayer(now->cal_val[now_ind / node_cnt] + child->base_val)];
}

// 更新計算權值
int updataCalVal(Trie *now, Trie *child, int now_ind) {
    return now->cal_val[now_ind / node_cnt] + child->base_val;
}

// BFS初始化可達矩陣
void BFS(Trie *root) {
    Trie *que[MAX_N * 3];
    int ind[MAX_N * 3];
    int vis[MAX_N * 3] = {0};
    int que_l = 0, que_r = 0;
    int ind_l = 0, ind_r = 0;
    que[que_r++] = root;
    ind[ind_r++] = 0; 
    while (ind_l < ind_r) {
        Trie *now = que[que_l++];
        int now_ind = ind[ind_l++];
        vis[now_ind] = 1;
        for (int i = 0 ; i < SIZE ; ++i) { 
            Trie *child;
            if (!now->childs[i]) {
                // 尋找失敗指針中是否出現childs[i], 如果沒出現過, 那麼就會走回root節點
                Trie *p_fail = now->fail;
                while (p_fail != NULL && p_fail->childs[i] == NULL) {
                    p_fail = p_fail->fail;
                }
                // 如果p_fail == NULL 那麼這個一定爲root
                if (p_fail == NULL) {
                    child = root;
                } else {
                    child = p_fail->childs[i];
                }
            } else {
                child = now->childs[i];
            }
            int child_ind = getChildIndex(now, child, now_ind);
            child->cal_val[child_ind / node_cnt] = updataCalVal(now, child, now_ind);
            CGMatrix.m[now_ind][child_ind]++;
            if (vis[child_ind] == 0) {
                ind[ind_r++] = child_ind;
                que[que_r++] = child;
                vis[child_ind] = 1;
            }
        }
    }
}

// 轉換函數 : 將ATCG轉換爲0123
void transStr(char *str) {
    for (int j = 0 ; str[j] ; ++j) {
        switch(str[j]) {
            case 'A' :
                str[j] = '0';
                break;
            case 'T' :
                str[j] = '1';
                break;
            case 'C' :
                str[j] = '2';
                break;
            case 'G' :
                str[j] = '3';
                break;
        }
    }
}

int main() {
    int n, L;
    char str[200];
    while (scanf("%d%d", &n, &L) != EOF) {
        Trie *root = new_node();
        node_cnt = 1;
        for (int i = 0 ; i < n ; ++i) {
            getchar();
            scanf("%s", str);
            transStr(str);
            insert(root, str);
        }
        build_automaton(root);
    
        // 設置矩陣大小
        mat_size = node_cnt * 3;
        init_unint_mat();

        BFS(root);
        CGMatrix = quick_mat_pow(CGMatrix, L);

        int ans = 0;
        for (int j = 2 * node_cnt ; j < mat_size ; ++j) {
            ans += CGMatrix.m[0][j];
            ans %= MOD;
        }
        printf("%d\n", ans);
        memset(CGMatrix.m, 0, sizeof(CGMatrix.m));
        clear(root);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章