字符串专题

字符串的问题

传送门(KMP)

void solve()
{
    int n, m, k;
    string s;
    cin >> s;
    s = " " + s;
    int len = s.size();
    vector<int> nxt(len+1);
    nxt[1] = 0;
    for (int i=2; i<len; ++i){
        nxt[i] = nxt[i-1];
        while (nxt[i] && s[i]!= s[nxt[i]+1]) nxt[i] = nxt[nxt[i]];
        nxt[i] += (s[i] == s[nxt[i]+1]);
    }
    vector<int> res(s.size()+1, 0);
    for (int i=0; i<s.size()-1; ++i){
        res[nxt[i]]++;
    }
    int ans = nxt[s.size()-1];
    if (ans <= 0){
        cout << "Just a legend\n";
    }
    else if (res[ans]){
        cout << s.substr(1, ans) << endl;
    }
    else if (nxt[ans]){
        cout << s.substr(1, nxt[ans]) << endl;
    }
    else{
        cout << "Just a legend\n";
    }
}

KMP字符串匹配 

void solve()
{
    int n, m, k;
    string s1, s2;
    cin >> s1 >> s2;
    s1 = " " + s1, s2 = " " + s2;
    int len = s2.size();
    vector<int> nxt(len+1, 0);
    for (int i=2; i<len; ++i){
        nxt[i] = nxt[i-1];
        while (nxt[i] > 0 && s2[i]!= s2[nxt[i]+1]) nxt[i] = nxt[nxt[i]];
        nxt[i] += (s2[i] == s2[nxt[i]+1]);
    }
    int len2 = s1.size();
    vector<int> ans;
    for (int i=1, j=0; i<len2; ++i){
        while (j && s1[i]!= s2[j+1]) j = nxt[j];
        j += (s1[i] == s2[j+1]);
        if (j == len-1){
            ans.pb(i-len+2);
            j = nxt[j];
        }
    }   
    if (!ans.empty()){
        for (auto x:ans){
            cout << x << endl;
        }
    }
    for (int i = 1; i<len; i++){
        cout << nxt[i] << " \n"[i == len-1];
    }
}
struct KMP{
    vector<int> nxt;
    vector<int> t;
    int len;
    string s;
    void clear(){
        len = 0;
        nxt.clear();
        t.clear();
    }
    // s = " " + s;
    KMP(string ss) : s(ss), len(ss.size()) {
        t.resize(len+1);
        nxt.resize(len+1);
        nxt[1] = 0;
        for (int i=2;i<len;i++){  // nxt数组初始化
            nxt[i] = nxt[i-1];
            while (nxt[i]&&ss[i]!=ss[nxt[i]+1]) nxt[i] = nxt[nxt[i]];
            nxt[i]+=(ss[i]==ss[nxt[i]+1]); // 找到匹配的加1
        }
    }
    vector<int> match(string ss){
        int len_s = ss.size();
        vector<int> start_pos;
        for (int i=1,j=0;i<len_s;i++){
            while (j && ss[i] != s[j+1])j = nxt[j];
            j += (ss[i] == s[j+1]);
            if (j == len-1){
                start_pos.push_back(i-len+2);
                j = nxt[j];
            }
        }
        return start_pos;
    }
};
void solve()
{
    int n, m, k;
    string s1, s2;
    cin >> s1 >> s2;
    s1 = " " + s1, s2 = " " + s2;
    KMP kmper(s2);
    auto ans = kmper.match(s1);
    if (!ans.empty()){
        for (auto x:ans){
            cout << x << endl;
        }
    }
    for (int i = 1; i<kmper.len; i++){
        cout << kmper.nxt[i] << " \n"[i == kmper.len-1];
    }
}

栗酱的数列

传送门(KMP)

int n, m, k;
template <typename T>
struct KMP {
    // 时间复杂度O(|S|+|T|)
    std::vector<int> nxt;
    std::vector<T> t;  // 使用模板参数T
    int len;
    std::vector<T> s;  // 使用模板参数T
    void clear() {
        len = 0;
        nxt.clear();
        t.clear();
    }
    // s = " " + s;
    KMP(std::vector<T> ss) : s(ss), len(ss.size()) {
        t.resize(len+1);
        nxt.resize(len+1);
        nxt[1] = 0;
        for (int i=2;i<len;i++){  // nxt数组初始化
            nxt[i] = nxt[i-1];
            while (nxt[i]&&s[i]!=s[nxt[i]+1]) nxt[i] = nxt[nxt[i]];
            nxt[i]+=(s[i]==s[nxt[i]+1]); // 找到匹配的加1
        }
    }
    // t = " " + t;
    std::vector<int> match(std::vector<T> t){   // 字符串匹配
        int len_s = t.size();
        std::vector<int> start_pos;
        for (int i=1,j=0;i<len_s;i++){
            while (j && (t[i] + s[j+1])%k != 0) j = nxt[j];
            j += ((t[i] + s[j+1])%k == 0);
            if (j == len-1){
                start_pos.push_back(i-len+2);
                j = nxt[j];
            }
        }
        return start_pos;
    }
    /* 循环周期 形如 abcab 中 abc 是一个合法周期 */
    std::vector<int> periodic(){
        std::vector<int> ret;
        int now = len-1;
        while (now){
            now = nxt[now];
            ret.push_back(len-1-now); // 会加一个本身,也是其一个周期
        }
        return ret;
    }
    /* 循环节 形如 acac 中ac、acac是循环节,aca不是*/
    std::vector<int> periodic_loop(){
        std::vector<int>ret ;
        for (int x :periodic()){
            if ((len-1)%x==0)ret.push_back(x); // 没有本身
        }
        return ret;
    }
    int min_periodic_loop(){  // 最小循环节
        return periodic_loop()[0];
    }
};
void solve()
{
    
    cin >> n >> m >> k;
    vector<int> a(n+1), b(m+1);
    for (int i = 1; i<=n; i++) cin >> a[i];
    for (int i = 1; i<=m; i++) cin >> b[i];
    vector<int> diff_a(n), diff_b(m);
    for (int i = 1; i<n; i++) {
        diff_a[i] = ((a[i]-a[i+1])%k+k)%k;
        // cout << diff_a[i] << " \n"[i==n-1];
    }
    for (int i = 1; i<m; i++) {
        diff_b[i] = ((b[i]-b[i+1])%k+k)%k;
        // cout << diff_b[i] << " \n"[i==m-1];
    }
    KMP<int> kmper(diff_b);
    vector<int> ans = kmper.nxt;
    // for (int i = 1; i<=n-1; i++){
    //     cout << ans[i] << " ";
    // }
    cout << kmper.match(diff_a).size() << endl;
    kmper.clear();
}

K匹配

传送门(KMP)

感觉题意描述的不是很好,很容易理解为是S里的子串中有多少个T里的子串,

但其实是S里的子串有多少个包含T这个串,这样我们每遇到一个符合的S子串就看前面有多少能和该结合成为S的子串,最后做个累加。

比如这里的last就为x-m+1个。

template <typename T>
struct KMP {
    // 时间复杂度O(|S|+|T|)
    vector<int> nxt;
    vector<T> t;  // 使用模板参数T
    int len;
    vector<T> s;  // 使用模板参数T
    void clear() {
        len = 0;
        nxt.clear();
        t.clear();
    }
    // s = " " + s;
    KMP(vector<T> ss) : s(ss), len(ss.size()) {
        t.resize(len+1);
        nxt.resize(len+1);
        nxt[1] = 0;
        for (int i=2;i<len;i++){  // nxt数组初始化
            nxt[i] = nxt[i-1];
            while (nxt[i]&&s[i]!=s[nxt[i]+1]) nxt[i] = nxt[nxt[i]];
            nxt[i]+=(s[i]==s[nxt[i]+1]); // 找到匹配的加1
        }
    }
    // t = " " + t;
    int match(vector<T> t){   // 字符串匹配
        int len_s = t.size();
        vector<int> f(len_s, 0);
        int last = 0;
        // vector<int> start_pos;
        for (int i=1,j=0;i<len_s;i++){
            while (j && t[i] != s[j+1]) j = nxt[j];
            j += (t[i] == s[j+1]);
            if (j == len-1){
                // start_pos.push_back(i-len+2);
                j = nxt[j];
                last = i-len+2;
            }
            f[i] = f[i-1]+last;
        }
        return f[len_s-1];
    }
};
void solve()
{
    int n, m, k;
    cin >> n >> k;
    string s1, s2;
    cin >> s1 >> s2;
    s1 = " " + s1;
    s2 = " " + s2;
    vector<char> traslate_s2(s2.begin(), s2.end()), traslate_s1(s1.begin(), s1.end());
    KMP<char> kmper(traslate_s2);
    cout << kmper.match(traslate_s1) << endl;
}

数一数

传送门(KMP)

这题也是比比较搞得,这里乘法得话我们就可以省略很多步骤,因为当这n个字符串的长度不相等时,f(si,sj)肯定为0,所以我们只要考虑最小的长度的字符串有几个匹配的,并且它这次匹配结果与该长度的一样,所以只用计算一次,当遇到长度也为最小时,直接输出结果即可,其他长度直接为0。感觉也可以直接hash看最小长度的字符串是否相等就可。

template <typename T>
struct KMP {
    // 时间复杂度O(|S|+|T|)
    vector<int> nxt;
    vector<T> t;  // 使用模板参数T
    int len;
    vector<T> s;  // 使用模板参数T
    void clear() {
        len = 0;
        nxt.clear();
        t.clear();
    }
    // s = " " + s;
    KMP(vector<T> ss) : s(ss), len(ss.size()) {
        t.resize(len+1);
        nxt.resize(len+1);
        nxt[1] = 0;
        for (int i=2;i<len;i++){  // nxt数组初始化
            nxt[i] = nxt[i-1];
            while (nxt[i]&&s[i]!=s[nxt[i]+1]) nxt[i] = nxt[nxt[i]];
            nxt[i]+=(s[i]==s[nxt[i]+1]); // 找到匹配的加1
        }
    }
    // t = " " + t;
    vector<int> match(vector<T> t){   // 字符串匹配
        int len_s = t.size();
        vector<int> start_pos;
        for (int i=1,j=0;i<len_s;i++){
            while (j && t[i] != s[j+1]) j = nxt[j];
            j += (t[i] == s[j+1]);
            if (j == len-1){
                start_pos.push_back(i-len+2);
                j = nxt[j];
            }
        }
        return start_pos;
    }
};
void solve()
{
    int n;
    cin >> n;
    vector<string> s(n);
    int mn = 1e9;
    for (int i = 0; i<n; i++){
        cin >> s[i];
        s[i] = " "+s[i];
        if (s[i].size() < mn){
            mn = s[i].size();
        }
    }
    int g = -1;
    for  (int i = 0; i<n; i++){
        if (s[i].size() == mn){
            if (g == -1){
                int res = 1;
                for (int j = 0; j<n; j++){
                    if (i == j) continue;
                    vector<char> traslate_i(s[i].begin(), s[i].end()), traslate_j(s[j].begin(), s[j].end());
                    KMP<char> kmp(traslate_i);
                    res = res*kmp.match(traslate_j).size()%mod;
                }
                
                cout << res << endl;
                g = res;
            }
            else cout << g << endl;
        }
        else cout << 0 << endl;
    }
}

假的字符串

传送门(字典树)

给定n个字符串,互不相等,你可以任意指定字符之间的大小关系(即重定义字典序),求有多少个串可能成为字典序最小的串,并输出它们

思路:这题的题面也是不好理解,简单就是这n个字符串都存在,且会相互影响,对于这n个分别判断其中一个能否是最小的字典序,

所以首先我们先将它们存入字典树中,再分别判断其能否成为,并且考虑字典的顺序,将在一个字母后的建边,并使用拓扑排序看是否有环,并在途中检查它的路径是否有完整的字符串,因为一个它的前缀的字典序必定比它小。

这道题会爆longlong,小心。。。

struct Trie {
    #define f(x, y) t[x].ch[y]
    // 以该点为结尾的字符串个数cnt、是否是完整字符串的标志ok,子节点的数组ch。
    struct Node {
        int cnt;
        bool ok;
        array<int,26> ch;
        Node(): cnt(0), ch{}, ok(false) {}
    };
    vector<Node> t;
    Trie() {
        init();
    }
    void init() {
       newNode();
    }
    // newNode()函数用于创建一个新的节点,并将其添加到字典树中,返回该节点的索引。
    int newNode() {
        t.push_back(Node());
        return t.size() - 1;
    }
    // 将字符转换为在字典树中的索引
    int get(char c) {
        return c - 'a';
    }
    // 插入函数
    // 同时更新每个节点的字符计数cnt和是否为完整字符串的标志ok
    void insert(const string &s) {
        int n = s.size() - 1;
        int p = 0;
        for (int i = 1; i <= n; i++) {
            int u = get(s[i]);
            if (f(p, u) == 0) {
                int k = newNode();
                f(p, u) = k;
            }
            p = f(p, u);
        }
        t[p].cnt++;
        t[p].ok = true;
    }
    // 查询函数,返回节点的cnt、ok、ch。
    // 返回该字符串最后一个字符所在节点的信息
    Node query(const string &s) {
        int n = s.size() - 1;
        int p = 0;
        for (int i = 1; i <= n; i++) {
            int u = get(s[i]);
            if (f(p, u) == 0) return Node(); // 不存在返回一个新节点
            p = f(p, u);
        }
        return t[p];
    }
    bool check(const string &s){
        int n = s.size() - 1;
        int p = 0;
        array<vector<int>, 26> g{};
        array<int, 26> din{};
        for (int i = 1; i<=n; i++){
            int u = get(s[i]);
            for (int j = 0; j<26; j++){
                if (j == u || f(p, j)==0)  continue;
                g[u].push_back(j);
                din[j]++;
            }
            p = f(p, u);
            if (t[p].ok && i != n) return false; // 防止是前缀
        }
        queue<int> q;
        for (int i = 0; i<26; i++){
            if (din[i] == 0) q.push(i);
        }
        while (!q.empty()){
            int u = q.front();
            q.pop();
            for (int v : g[u]){
                if (--din[v] == 0){
                    q.push(v);
                }
            }
        }
        bool ok = true;
        for (int i = 0; i<26; i++){
            ok &= (din[i] == 0);
        }
        return ok;
    }
    #undef f
};
void solve()
{
    int n, m, k;
    cin >> n;
    vector<string> s(n+1);
    Trie trie;
    for (int i = 1; i<=n; i++){
        cin >> s[i];
        s[i] = " "+s[i];
        trie.insert(s[i]);
    }
    vector<string> ans;
    for (int i = 1; i<=n; i++){
        if (trie.check(s[i])){
            ans.push_back(s[i]);
        }
    }
    cout << ans.size() << endl;
    for (int i = 0; i<ans.size(); i++){
        cout << ans[i].substr(1, ans[i].size()-1) << endl;
    }
}

奶牛异或

传送门(字典树)

思路:首先求出异或前缀和,每个区间异或和可以表示成两个前缀和的异或,问题转换成n+1个数字重心选两个做异或运算的最大值,可以用01Trie解决。相同宽度的两个二进制数字的大小关系等价于字典序关系。枚举一个数字x,然后寻找与它异或结果最大的另一个数字y。从高到低考虑x的每一个二进制位bit,如果y的这一位也是bit,则异或结果为0,反之为1。所以在01Trie上寻找y:从跟出发,有 !bit 边,则走 !bit 边,否则只能走bit边。这里将叶子节点来记录位置,便于了后于访问。并且注意要先建0的分支,构建一条全是0的分支,表示从第一个开始异或。

struct Trie {
    #define f(x, y) t[x].ch[y]
    
    struct Node {
        array<int,2> ch;
        Node(): ch{} {}
    };
    vector<Node> t;
    vector<int> pos;
    Trie() {
        init();
    }
    void init() {
       newNode();
    }
    // newNode()函数用于创建一个新的节点,并将其添加到字典树中,返回该节点的索引。
    int newNode() {
        t.push_back(Node());
        pos.push_back(0);
        return t.size() - 1;
    }
    // get()函数用于获取x的第y位的值,0或1。
    int get(int x, int y) {
        return (x>>y)&1;
    }
    // 插入函数
    void insert(const int &x, const int &location) {
        // cout << "insert " << x << " " << location << endl;
        int p = 0;
        for (int i = 20; i>=0; i--){
            int v = get(x, i);
            if (f(p, v) == 0) f(p, v) = newNode();
            p = f(p, v);
        }
        pos[p] = location; //给叶节点赋值为位置,便于后续访问
    }
    // 查询函数,返回最大异或值和该位置的位置
    pair<int, int> query(const int &x) {
        int p = 0;
        int mx = 0;
        for (int i = 20; i>=0; i--){
            int v = get(x, i);
            if (f(p, !v)){
                p = f(p, !v);
                mx |= (1<<i);
            }
            else {
                p = f(p, v);
            }
        }
        return make_pair(mx, pos[p]);
    }
    #undef f
};
void solve()
{
    int n, m, k;
    cin >> n;
    Trie trie;
    vector<int> a(n+1), sum_xor(n+1);
    int ans = -1, L = 0, R = n;
    trie.insert(0, 1); // 这部步很重要,因为构建一条全是0的分支,表示从第一个开始异或
    for (int i = 1; i<=n; i++){
        cin >> a[i];
        sum_xor[i] = sum_xor[i-1] ^ a[i];
        auto [XOR, p] = trie.query(sum_xor[i]);
        if (XOR > ans){
            ans = XOR;
            L = p, R = i;
        }
        trie.insert(sum_xor[i], i+1);
    }
    cout << ans << " " << L << " " << R << endl;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wirepuller_king

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值