PAM

F-[APIO2014]回文串

 一个板子题, 维护一下每个节点出现次数,倒着把次数往 fail 上推, 长度可以直接维护

#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

 板子题,开个桶维护一下每个节点字母出现情况就行

#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是否刚好是当前节点代表字符串长度一半

#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


 

 显然这个串会是一个回文串 + 左右两边加一些字符形式,转移大概这样(洛谷题解上的)

 

#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 有两个根

这里给出第一种做法代码

#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

和上题基本一致,改变一下贡献计算方式即可

这里我用第一种做法不知道为什么一直过不了,改成第二种做法才能通过,所以给出第二种做法代码

#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

 

posted @ 2024-03-21 21:00  zhujio  阅读(1)  评论(0编辑  收藏  举报