PAM

zhujio發表於2024-03-21

F-[APIO2014]迴文串

一個板子題, 維護一下每個節點出現次數,倒著把次數往 fail 上推, 長度可以直接維護

PAM
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
#define int long long
typedef long long ll;

const int N = 3e5 + 100;
struct Palindromic_AutoMaton {
    //basic
    int s[N], now;
    int nxt[N][26], fail[N], len[N], last, tot;
    // extension
    int num[N];/*節點代表的所有迴文串出現次數*/
    void clear() {
        // 1節點 : 奇數長度 root 
        // 0節點 : 偶數長度 root
        s[0] = len[1] = -1;
        fail[0] = tot = now = 1;
        last = len[0] = 0;
        memset(nxt[0], 0, sizeof nxt[0]);
        memset(nxt[1], 0, sizeof nxt[1]);
    }
    Palindromic_AutoMaton() {clear();}
    // 建一個新節點, 長度為 nowlen
    int newnode(int nowlen) {
        tot++;
        memset(nxt[tot], 0, sizeof nxt[tot]);
        fail[tot] = num[tot] = 0;
        len[tot] = nowlen;
        return tot;
    }
    // 找字尾迴文
    int get_fail(int x) {
        // now - 1 是當前節點
        // x 是 s 串中 now - 1 的前一個節點
        // 設當前節點為 p, 那麼 s[p] 對稱節點就是 s[p - len[x] - 1]
        // check 出最長迴文字尾
        while (s[now - len[x] - 2] != s[now - 1]) x = fail[x];
        return x;
    }
    void add(int ch) {
        s[now++] = ch;
        int cur = get_fail(last);
        if (!nxt[cur][ch]) {
            int tt = newnode(len[cur] + 2);
            fail[tt] = nxt[get_fail(fail[cur])][ch];
            nxt[cur][ch] = tt;
        }
        last = nxt[cur][ch]; num[last]++;
    }
    void build() {
        //fail[i] < i,拓撲更新可以單調掃描。
        for (int i = tot; i >= 2; i--) {
            num[fail[i]] += num[i];
        }
        num[0] = num[1] = 0;
    }
    void init(char* ss) {
        while (*ss) {
            add(*ss - 'a'); ss++;
        }
    }
    void init(string str) {
        for (int i = 0; i < str.size(); i++) {
            add(str[i] - 'a');
        }
    }
    void query() {
        int ans = 1;
        for (int i = 2; i <= tot; i++) ans = max(ans, len[i] * num[i]);
        cout << ans << endl;
    }
} PAM;

char s[N];

void solve() {
    int n; cin >> n;
    cin >> s;
    PAM.init(s);
    PAM.build();
    PAM.query();
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    solve();
    return 0;
}
View Code

G-Colorful String

板子題,開個桶維護一下每個節點字母出現情況就行

PAM
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
#define int long long
typedef long long ll;

const int N = 3e5 + 100;
struct Palindromic_AutoMaton {
    //basic
    int s[N], now;
    int nxt[N][26], fail[N], len[N], last, tot;
    // extension
    int num[N][26], cnt[N];
    void clear() {
        // 1節點 : 奇數長度 root 
        // 0節點 : 偶數長度 root
        s[0] = len[1] = -1;
        fail[0] = tot = now = 1;
        last = len[0] = 0;
        memset(nxt[0], 0, sizeof nxt[0]);
        memset(nxt[1], 0, sizeof nxt[1]);
    }
    Palindromic_AutoMaton() {clear();}
    // 建一個新節點, 長度為 nowlen
    int newnode(int nowlen) {
        tot++;
        memset(nxt[tot], 0, sizeof nxt[tot]);
        fail[tot] = 0;
        len[tot] = nowlen;
        return tot;
    }
    // 找字尾迴文
    int get_fail(int x) {
        // now - 1 是當前節點
        // x 是 s 串中 now - 1 的前一個節點
        // 設當前節點為 p, 那麼 s[p] 對稱節點就是 s[p - len[x] - 1]
        // check 出最長迴文字尾
        while (s[now - len[x] - 2] != s[now - 1]) x = fail[x];
        return x;
    }
    void add(int ch) {
        s[now++] = ch;
        int cur = get_fail(last);
        if (!nxt[cur][ch]) {
            int tt = newnode(len[cur] + 2);
            fail[tt] = nxt[get_fail(fail[cur])][ch];
            nxt[cur][ch] = tt;
            for (int i = 0; i < 26; i++) num[tt][i] = num[cur][i];
            num[tt][ch] = 1;
        }
        last = nxt[cur][ch]; cnt[last]++;
    }
    void build() {
        //fail[i] < i,拓撲更新可以單調掃描。
        for (int i = tot; i >= 2; i--) cnt[fail[i]] += cnt[i];
        cnt[1] = cnt[0] = 1;
        int ans = 0;
        for (int i = tot; i >= 2; i--) {
            int sum = 0;
            for (int j = 0; j < 26; j++) {
                if (num[i][j]) sum++;
            }
            ans += sum * cnt[i];
        }
        cout << ans << endl;
    }
    void init(char* ss) {
        while (*ss) {
            add(*ss - 'a'); ss++;
        }
    }
    void init(string str) {
        for (int i = 0; i < str.size(); i++) {
            add(str[i] - 'a');
        }
    }
} PAM;

void solve() {
    string s; cin >> s;
    PAM.init(s); PAM.build();
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    solve();
    return 0;
}
View Code


P4287 [SHOI2011] 雙倍迴文

引入了 trans 陣列概念, trans 指標指向不超過迴文串長度一半的最長迴文字尾節點

我們對於每個節點檢查是否長度為偶數,trans是否剛好是當前節點代表字串長度一半

PAM
#include <bits/stdc++.h>
using namespace std;
#define endl "\n"
#define int long long

const int N = 5e5 + 100;
struct Palindromic_AutoMaton {
    //basic
    int s[N], now;
    int nxt[N][26], fail[N], len[N], tran[N], last, tot;
    // extension
    int num[N];/*節點代表的所有迴文串出現次數*/
    void clear() {
        // 1節點 : 奇數長度 root 
        // 0節點 : 偶數長度 root
        s[0] = len[1] = -1;
        fail[0] = tot = now = 1;
        last = len[0] = 0;
        memset(nxt[0], 0, sizeof nxt[0]);
        memset(nxt[1], 0, sizeof nxt[1]);
    }
    Palindromic_AutoMaton() {clear();}
    // 建一個新節點, 長度為 nowlen
    int newnode(int nowlen) {
        tot++;
        memset(nxt[tot], 0, sizeof nxt[tot]);
        fail[tot] = num[tot] = 0;
        len[tot] = nowlen;
        return tot;
    }
    // 找字尾迴文
    int get_fail(int x) {
        // now - 1 是當前節點
        // x 是 s 串中 now - 1 的前一個節點
        // 設當前節點為 p, 那麼 s[p] 對稱節點就是 s[p - len[x] - 1]
        // check 出最長迴文字尾
        while (s[now - len[x] - 2] != s[now - 1]) x = fail[x];
        return x;
    }
    // tran 指標指向不超過迴文串長度一半的最長迴文字尾節點
    void trans(int x, int y, int pos, int ch) {
        if (len[x] <= 2) tran[x] = fail[x];
        else {
            int t = tran[y];
            while (s[pos - 1 - len[t]] != s[pos] || (len[t] + 2) * 2 > len[x]) t = fail[t]; 
            tran[x] = nxt[t][ch];
        }
    }
    void add(int ch, int pos) {
        s[now++] = ch;
        int cur = get_fail(last);
        if (!nxt[cur][ch]) {
            int tt = newnode(len[cur] + 2);
            fail[tt] = nxt[get_fail(fail[cur])][ch];
            nxt[cur][ch] = tt;
            trans(tt, cur, pos, ch);
        }
        last = nxt[cur][ch];
    }
    void build() {
        //fail[i] < i,拓撲更新可以單調掃描。
        for (int i = tot; i >= 2; i--) {
            num[fail[i]] += num[i];
        }
        num[0] = num[1] = 0;
    }
    void init(char* str, int n) {
        for (int i = 1; i <= n; i++) add(str[i] - 'a', i);
    }
    void init(string str, int n) {
        for (int i = 1; i <= n; i++) {
            add(str[i] - 'a', i);
        }
    }
    // 拓撲遍歷trie樹
    void topo() {
        queue<int> q;
        for (int i = 0; i < 26; i++) {
            // 長度為奇數的
            if (nxt[1][i]) q.push(nxt[1][i]);
            // 長度為偶數的
            if (nxt[0][i]) q.push(nxt[0][i]);
        }
        while (!q.empty()) {
            int x = q.front(); q.pop();
            for (int i = 0; i < 26; i++) {
                int y = nxt[x][i];
                if (!y) continue;
                q.push(y);
            }
        }
    }
    void query() {
        int ans = 0;
        for (int i = 2; i <= tot; i++) {
            int x = tran[i];
            if (len[x] % 2 == 0 && len[i] == len[x] * 2) ans = max(ans, len[i]);
        }
        cout << ans << endl;
    }
} PAM;

char s[N];

void solve() {
    int n; cin >> n;    
    cin >> (s + 1);
    // string s; cin >> s;
    // int n = s.size();
    // s = " " + s;
    PAM.init(s, n);
    PAM.query();
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    // int T; cin >> T;
    // while (T--) solve();
    solve();
    return 0;  
}
View Code

P4762 [CERC2014] Virus synthesis

做法是利用 trans 陣列性質進行 dp


顯然這個串會是一個迴文串 + 左右兩邊加一些字元形式,轉移大概這樣(洛谷題解上的)

PAM
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
#define int long long
typedef long long ll;

const int N = 1e5 + 100;

int f[N];
map<char, int> mp;

struct Palindromic_AutoMaton {
    //basic
    int s[N], now;
    int nxt[N][5], fail[N], len[N], last, tot;
    // extension
    int tran[N];
    void clear() {
        // 1節點 : 奇數長度 root 
        // 0節點 : 偶數長度 root
        s[0] = len[1] = -1;
        fail[0] = tot = now = 1;
        last = len[0] = 0;
        memset(nxt[0], 0, sizeof nxt[0]);
        memset(nxt[1], 0, sizeof nxt[1]);
    }
    Palindromic_AutoMaton() {clear();}
    // 建一個新節點, 長度為 nowlen
    int newnode(int nowlen) {
        tot++;
        memset(nxt[tot], 0, sizeof nxt[tot]);
        fail[tot] = 0;
        len[tot] = nowlen;
        return tot;
    }
    // 找字尾迴文
    int get_fail(int x) {
        // now - 1 是當前節點
        // x 是 s 串中 now - 1 的前一個節點
        // 設當前節點為 p, 那麼 s[p] 對稱節點就是 s[p - len[x] - 1]
        // check 出最長迴文字尾
        while (s[now - len[x] - 2] != s[now - 1]) x = fail[x];
        return x;
    }
    // tran 指標指向不超過迴文串長度一半的最長迴文字尾節點
    void trans(int x, int y, int pos, int ch) {
        if (len[x] <= 2) tran[x] = fail[x];
        else {
            int t = tran[y];
            while (s[pos - 1 - len[t]] != s[pos] || (len[t] + 2) * 2 > len[x]) t = fail[t]; 
            tran[x] = nxt[t][ch];
        }
    }
    void add(int ch, int pos) {
        s[now++] = ch;
        int cur = get_fail(last);
        if (!nxt[cur][ch]) {
            int tt = newnode(len[cur] + 2);
            fail[tt] = nxt[get_fail(fail[cur])][ch];
            nxt[cur][ch] = tt;
            trans(tt, cur, pos, ch);
        }
        last = nxt[cur][ch];
    }
    void build(int n) {
        //fail[i] < i,拓撲更新可以單調掃描。
        for (int i = 2; i <= tot; i++) f[i] = len[i];
        queue<int> q;
        int ans = n;
        for (int i = 1; i <= 4; i++) {
            if (nxt[0][i]) q.push(nxt[0][i]);
        }
        while (!q.empty()) {
            int x = q.front(); q.pop();
            f[x] = min(f[x], len[x] / 2 - len[tran[x]] + f[tran[x]] + 1);
            ans = min(ans, f[x] + n - len[x]);
            for (int i = 1; i <= 4; i++) {
                int y = nxt[x][i];
                if (!y) continue;
                f[y] = min(f[y], f[x] + 1);
                q.push(y);
            }
        }
        cout << ans << endl;
    }
    void init(string str, int n) {
        for (int i = 1; i <= n; i++) {
            add(mp[str[i]], i);
        }
    }
    void init(char* ss, int n) {
        for (int i = 1; i <= n; i++) add(mp[ss[i]], i);
    }
} PAM;

void solve() {
    string s; cin >> s;
    int n = s.size();
    s = " " + s;
    mp['A'] = 1; mp['T'] = 2; mp['C'] = 3; mp['G'] = 4;
    PAM.clear();
    PAM.init(s, n); PAM.build(n);
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T; cin >> T;
    while (T--) solve();
    return 0;
}
View Code


P5555 秩序魔咒

簡化下題意大概就是給你兩個字串,找出兩字串所有公共迴文字串中的最大長度, 以及有多少種公共迴文串是最大長度

這種題有兩種做法一種是建一個 PAM,開兩個陣列表示第一個串中是否有這個節點,另外一個就是表示第二個串是否有這個節點

第一個串加完節點後我們把 PAM 中的 now 清空即可, 程式碼比較詳細

第二種做法就是我們建兩個 PAM,一起去跑 dfs,當兩串都有某個節點就繼續 dfs,有個細節是要dfs 0 , 1兩個節點,因為 PAM 有兩個根

這裡給出第一種做法程式碼

PAM
#include <bits/stdc++.h>
using namespace std;
#define endl "\n"
#define int long long

const int N = 6e6 + 100;

int ans, res, vis[N][2];

// 有點逆天, 為什麼空間要開十倍?
// 對於兩個串之間的連線,並不需要真的去加一個點(雖然不知道為什麼真的加只能拿95分)
// 我們在第一個串 add 完以後把 now clear一下就行, 因為 now 是 PAM 存當前串的狀態的指標
// 我們把它設定為 1 就相當於清空了板子中 s,getfail就不會是找到兩串連線得到的串上面去

struct Palindromic_AutoMaton {
    //basic
    int s[N], now;
    int nxt[N][30], fail[N], len[N], last, tot;
    void clear() {
        // 1節點 : 奇數長度 root 
        // 0節點 : 偶數長度 root
        s[0] = len[1] = -1;
        fail[0] = tot = now = 1;
        last = len[0] = 0;
        memset(nxt[0], 0, sizeof nxt[0]);
        memset(nxt[1], 0, sizeof nxt[1]);
    }
    Palindromic_AutoMaton() {clear();}
    // 建一個新節點, 長度為 nowlen
    int newnode(int nowlen) {
        tot++;
        memset(nxt[tot], 0, sizeof nxt[tot]);
        fail[tot] = 0;
        len[tot] = nowlen;
        return tot;
    }
    // 找字尾迴文
    int get_fail(int x) {
        // now - 1 是當前節點
        // x 是 s 串中 now - 1 的前一個節點
        // 設當前節點為 p, 那麼 s[p] 對稱節點就是 s[p - len[x] - 1]
        // check 出最長迴文字尾
        while (s[now - len[x] - 2] != s[now - 1]) x = fail[x];
        return x;
    }
    void add(int ch, int op) {
        s[now++] = ch;
        int cur = get_fail(last);
        if (!nxt[cur][ch]) {
            int tt = newnode(len[cur] + 2);
            fail[tt] = nxt[get_fail(fail[cur])][ch];
            nxt[cur][ch] = tt;
        }
        last = nxt[cur][ch];
        vis[last][op] = 1;
    }
} PAM;

char s1[N], s2[N];

void solve() {
    PAM.clear();
    int n, m; cin >> n >> m;
    ans = res = 0;
    cin >> s1 >> s2;
    for (int i = 0; i < n; i++) {
        PAM.add(s1[i] - 'a', 0);
    }
    // PAM.add(29, 0);
    PAM.now = 1;
    for (int i = 0; i < m; i++) {
        PAM.add(s2[i] - 'a', 1);
    }
    auto &len = PAM.len;
    for (int i = 0; i <= PAM.tot; i++) {
        if (vis[i][0] && vis[i][1]) {
            if (len[i] > ans) ans = len[i], res = 1;
            else if (len[i] == ans) res++;
        }
    }
    cout << ans << ' ' << res << endl;
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    // int T; cin >> T;
    // while (T--) solve();
    solve();
    return 0;  
}
View Code

P5685 [JSOI2013] 快樂的 JYY

和上題基本一致,改變一下貢獻計算方式即可

這裡我用第一種做法不知道為什麼一直過不了,改成第二種做法才能透過,所以給出第二種做法程式碼

PAM
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"

const int N = 5e6 + 100;

struct Palindromic_AutoMaton {
    //basic
    int s[N], now;
    int nxt[N][30], fail[N], len[N], tran[N], last, tot;
    // extension
    int num[N];/*節點代表的所有迴文串出現次數*/
    void clear() {
        // 1節點 : 奇數長度 root 
        // 0節點 : 偶數長度 root
        s[0] = len[1] = -1;
        fail[0] = tot = now = 1;
        last = len[0] = 0;
        memset(nxt[0], 0, sizeof nxt[0]);
        memset(nxt[1], 0, sizeof nxt[1]);
    }
    Palindromic_AutoMaton() {clear();}
    // 建一個新節點, 長度為 nowlen
    int newnode(int nowlen) {
        tot++;
        memset(nxt[tot], 0, sizeof nxt[tot]);
        fail[tot] = num[tot] = 0;
        len[tot] = nowlen;
        return tot;
    }
    // 找字尾迴文
    int get_fail(int x) {
        // now - 1 是當前節點
        // x 是 s 串中 now - 1 的前一個節點
        // 設當前節點為 p, 那麼 s[p] 對稱節點就是 s[p - len[x] - 1]
        // check 出最長迴文字尾
        while (s[now - len[x] - 2] != s[now - 1]) x = fail[x];
        return x;
    }
    // tran 指標指向不超過迴文串長度一半的最長迴文字尾節點
    void trans(int x, int y, int pos, int ch) {
        if (len[x] <= 2) tran[x] = fail[x];
        else {
            int t = tran[y];
            while (s[pos - 1 - len[t]] != s[pos] || (len[t] + 2) * 2 > len[x]) t = fail[t]; 
            tran[x] = nxt[t][ch];
        }
    }

    void build() {
        for (int i = tot; i >= 2; i--) {
            num[fail[i]] += num[i];
        }
        num[0] = num[1] = 0;
    }

    void add(int ch, int pos, int op) {
        s[now++] = ch;
        int cur = get_fail(last);
        if (!nxt[cur][ch]) {
            int tt = newnode(len[cur] + 2);
            fail[tt] = nxt[get_fail(fail[cur])][ch];
            nxt[cur][ch] = tt;
            trans(tt, cur, pos, ch);
        }
        last = nxt[cur][ch];
        num[last]++;
    }
    void init(char* str, int n, int op) {
        for (int i = 1; i <= n; i++) add(str[i] - 'A', i, op);
    }
    // 拓撲遍歷trie樹
    void topo() {
        queue<int> q;
        for (int i = 0; i < 26; i++) {
            // 長度為奇數的
            if (nxt[1][i]) q.push(nxt[1][i]);
            // 長度為偶數的
            if (nxt[0][i]) q.push(nxt[0][i]);
        }
        while (!q.empty()) {
            int x = q.front( ); q.pop();
            for (int i = 0; i < 26; i++) {
                int y = nxt[x][i];
                if (!y) continue;
                q.push(y);
            }
        }
    }
} P1, P2;

char s1[N], s2[N];

int ans;
void dfs(int x, int y) {
    ans += P1.num[x] * P2.num[y];
    for (int i = 0; i < 26; i++) {
        if (P1.nxt[x][i] && P2.nxt[y][i]) dfs(P1.nxt[x][i], P2.nxt[y][i]);
    }
}

void solve() {
    cin >> (s1 + 1) >> (s2 + 1);
    int n = strlen(s1 + 1), m = strlen(s2 + 1);
    for (int i = 1; i <= n; i++) P1.add(s1[i] - 'A', i, 0);
    P1.build();
    for (int i = 1; i <= m; i++) P2.add(s2[i] - 'A', i, 1);
    P2.build();
    dfs(0, 0); dfs(1, 1);
    cout << ans << endl;
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    solve();
    return 0;
}
View Code