【小结】AC自动机

DFA AC 自动机

  • 考虑以下单词: {she, he, her}
  • 我们先画出它Trie树的模样

  • 留个板子

/* **********************************************

  File Name: ac_automata.cpp

  Auther: zhengdongjian@tju.edu.cn

  Created Time: 2015年08月14日 星期五 08时41分23秒

*********************************************** */
#include <bits/stdc++.h>
using namespace std;

typedef pair<int, int> P;
const double EPS = 1e-8;
const double PI = acos(-1.0);

const int MAX = 500007;
const int MAXD = 26; //26 alphas
struct Trie {
    /* 
     * nxt & end is used in trie
     * fail is for ac automata
     */
    int nxt[MAX][MAXD], fail[MAX], end[MAX];
    int root, L; //root node, length(the nodes has been malloc)[0, L]
    int newnode() {
        memset(nxt[L], -1, sizeof(int) * MAXD);
        end[L++] = 0;
        return L - 1;
    }
    void clear() {
        L = 0;
        root = newnode();
    }
    void insert(char* buf) {
        int len = strlen(buf);
        int now = root;
        for (int i = 0; i < len; ++i) {
            if (nxt[now][buf[i] - 'a'] == -1) {
                nxt[now][buf[i] - 'a'] = newnode();
            }
            now = nxt[now][buf[i] - 'a'];
        }
        ++end[now];
    }
    void build() {
        queue<int> Q;
        fail[root] = root;
        for (int i = 0; i < MAXD; ++i) {
            if (nxt[root][i] == -1) {
                nxt[root][i] = root;
            } else {
                fail[nxt[root][i]] = root;
                Q.push(nxt[root][i]);
            }
        }

        while (!Q.empty()) {
            int now = Q.front();
            Q.pop();

            for (int i = 0; i < MAXD; ++i) {
                if (nxt[now][i] == -1) {
                    nxt[now][i] = nxt[fail[now]][i];
                } else {
                    fail[nxt[now][i]] = nxt[fail[now]][i];
                    Q.push(nxt[now][i]);
                }
            }
        }
    }
    int query(char* buf, int len = -1) {
        if (len == -1) {
            len = strlen(buf);
        }
        int now = root;
        int res = 0;
        for (int i = 0; i < len; ++i) {
            now = nxt[now][buf[i] - 'a'];
            int tmp = now;
            while (tmp != root) {
                res += end[tmp];
                end[tmp] = 0; //不重复,若可重复此处不置0即可
                tmp = fail[tmp];
            }
        }
        return res;
    }
    void debug() {
        for (int i = 0; i < L; ++i) {
            printf("%d, %d, %d, [%d", i, fail[i], end[i], nxt[i][0]);
            for (int j = 1; j < MAXD; ++j) {
                printf(" %d", nxt[i][j]);
            }
            puts("]");
        }
    }
} ac;

const int MAXL = 64;
char str[MAXL];
char buf[MAX << 1];

int main() {
    int T;
    scanf(" %d", &T);
    while (T--) {
        int n;
        scanf(" %d", &n);
        ac.clear();
        for (int i = 0; i < n; ++i) {
            scanf(" %s", str);
            ac.insert(str);
        }
        ac.build();

        scanf(" %s", buf);
        printf("%d\n", ac.query(buf));
    }
    return 0;
}

几个简单的小题目

  • hdu2222,First attempt
/* **********************************************

  File Name: ac_automata.cpp => hdu2222

  Auther: zhengdongjian@tju.edu.cn

  Created Time: 2015年08月14日 星期五 08时41分23秒

*********************************************** */
#include <bits/stdc++.h>
using namespace std;

typedef pair<int, int> P;
const double EPS = 1e-8;
const double PI = acos(-1.0);

const int MAX = 500007;
const int MAXD = 26; //26 alphas
struct Trie {
    /* 
     * nxt & end is used in trie
     * fail is for ac automata
     */
    int nxt[MAX][MAXD], fail[MAX], end[MAX];
    int root, L; //root node, length(the nodes has been malloc)[0, L]
    int newnode() {
        memset(nxt[L], -1, sizeof(int) * MAXD);
        end[L++] = 0;
        return L - 1;
    }
    void clear() {
        L = 0;
        root = newnode();
    }
    void insert(char* buf) {
        int len = strlen(buf);
        int now = root;
        for (int i = 0; i < len; ++i) {
            if (nxt[now][buf[i] - 'a'] == -1) {
                nxt[now][buf[i] - 'a'] = newnode();
            }
            now = nxt[now][buf[i] - 'a'];
        }
        ++end[now];
    }
    void build() {
        queue<int> Q;
        fail[root] = root;
        for (int i = 0; i < MAXD; ++i) {
            if (nxt[root][i] == -1) {
                nxt[root][i] = root;
            } else {
                fail[nxt[root][i]] = root;
                Q.push(nxt[root][i]);
            }
        }

        while (!Q.empty()) {
            int now = Q.front();
            Q.pop();

            for (int i = 0; i < MAXD; ++i) {
                if (nxt[now][i] == -1) {
                    nxt[now][i] = nxt[fail[now]][i];
                } else {
                    fail[nxt[now][i]] = nxt[fail[now]][i];
                    Q.push(nxt[now][i]);
                }
            }
        }
    }
    int query(char* buf, int len = -1) {
        if (len == -1) {
            len = strlen(buf);
        }
        int now = root;
        int res = 0;
        for (int i = 0; i < len; ++i) {
            now = nxt[now][buf[i] - 'a'];
            int tmp = now;
            while (tmp != root) {
                res += end[tmp];
                end[tmp] = 0; //不重复,若可重复此处不置0即可
                tmp = fail[tmp];
            }
        }
        return res;
    }
    void debug() {
        for (int i = 0; i < L; ++i) {
            printf("%d, %d, %d, [%d", i, fail[i], end[i], nxt[i][0]);
            for (int j = 1; j < MAXD; ++j) {
                printf(" %d", nxt[i][j]);
            }
            puts("]");
        }
    }
} ac;

const int MAXL = 64;
char str[MAXL];
char buf[MAX << 1];

int main() {
    int T;
    scanf(" %d", &T);
    while (T--) {
        int n;
        scanf(" %d", &n);
        ac.clear();
        for (int i = 0; i < n; ++i) {
            scanf(" %s", str);
            ac.insert(str);
        }
        ac.build();

        scanf(" %s", buf);
        printf("%d\n", ac.query(buf));
    }
    return 0;
}
  • hdu2896
  • 病毒保证不同,简单统计,随便搞
/* **********************************************

  File Name: 2896.cpp

  Auther: zhengdongjian@tju.edu.cn

  Created Time: 2015年08月14日 星期五 11时25分51秒

*********************************************** */
#include <bits/stdc++.h>
using namespace std;

typedef pair<int, int> P;
const double EPS = 1e-8;
const double PI = acos(-1.0);
const int MAX = 100007;
const int MAXD = 128;
struct Trie {
    int nxt[MAX][MAXD], fail[MAX], end[MAX];
    int root, L;

    int newnode() {
        memset(nxt[L], -1, sizeof(int) * MAXD);
        end[L++] = -1;
        return L - 1;
    }

    void clear() {
        L = 0;
        root = newnode();
    }

    void insert(char* buf, int _end) {
        int len = strlen(buf);
        int now = root;
        for (int i = 0; i < len; ++i) {
            if (nxt[now][(int)buf[i]] == -1) {
                nxt[now][(int)buf[i]] = newnode();
            }
            now = nxt[now][(int)buf[i]];
        }
        end[now] = _end;
    }

    void build() {
        queue<int> Q;
        fail[root] = root;
        for (int i = 0; i < MAXD; ++i) {
            if (nxt[root][i] == -1) {
                nxt[root][i] = root;
            } else {
                fail[nxt[root][i]] = root;
                Q.push(nxt[root][i]);
            }
        }

        while (!Q.empty()) {
            int now = Q.front();
            Q.pop();

            for (int i = 0; i < MAXD; ++i) {
                if (nxt[now][i] == -1) {
                    nxt[now][i] = nxt[fail[now]][i];
                } else {
                    fail[nxt[now][i]] = nxt[fail[now]][i];
                    Q.push(nxt[now][i]);
                }
            }
        }
    }
    set<int> query(char* buf) {
        int len = strlen(buf);
        int now = root;
        set<int> res;
        for (int i = 0; i < len; ++i) {
            now = nxt[now][(int)buf[i]];
            int tmp = now;
            while (tmp != root) {
                if (~end[tmp]) {
                    res.insert(end[tmp]);
                }
                tmp = fail[tmp];
            }
        }
        return res;
    }
} ac;
char buf[MAX];

int main() {
    int n, m;
    while (~scanf(" %d", &n)) {
        ac.clear();
        for (int i = 1; i <= n; ++i) {
            scanf(" %s", buf);
            ac.insert(buf, i);
        }
        ac.build();
        scanf(" %d", &m);
        int sum = 0;
        for (int i = 1; i <= m; ++i) {
            scanf(" %s", buf);
            auto v = ac.query(buf);
            if (!v.empty()) {
                ++sum;
                printf("web %d:", i);
                for (auto it = v.begin(); it != v.end(); ++it) {
                    printf(" %d", *it);
                }
                puts("");
            }
        }
        printf("total: %d\n", sum);
    }
    return 0;
}
  • hdu3065
  • 要打印匹配串:打标记,Trie上节点打前驱和字符标记。P.S.空间上还可以优化
/* **********************************************

  File Name: 3065.cpp

  Auther: zhengdongjian@tju.edu.cn

  Created Time: 2015年08月14日 星期五 11时46分10秒

*********************************************** */
#include <bits/stdc++.h>
using namespace std;

typedef pair<int, int> P;
const double EPS = 1e-8;
const double PI = acos(-1.0);

const int MAX = 50007;
const int MAXD = 128; //26 alphas
struct Trie {
    /* 
     * nxt & end is used in trie
     * fail is for ac automata
     */
    int nxt[MAX][MAXD], fail[MAX], end[MAX], pre[MAX];
    char dad[MAX];
    int root, L; //root node, length(the nodes has been malloc)[0, L]
    int newnode() {
        memset(nxt[L], -1, sizeof(int) * MAXD);
        pre[L] = -1;
        end[L++] = 0;
        return L - 1;
    }
    void clear() {
        L = 0;
        root = newnode();
    }
    void insert(char* buf) {
        int len = strlen(buf);
        int now = root;
        for (int i = 0; i < len; ++i) {
            if (nxt[now][(int)buf[i]] == -1) {
                nxt[now][(int)buf[i]] = newnode();
                pre[nxt[now][(int)buf[i]]] = now;
                dad[nxt[now][(int)buf[i]]] = buf[i];
            }
            now = nxt[now][(int)buf[i]];
        }
        ++end[now];
    }

    void build() {
        queue<int> Q;
        fail[root] = root;
        for (int i = 0; i < MAXD; ++i) {
            if (nxt[root][i] == -1) {
                nxt[root][i] = root;
            } else {
                fail[nxt[root][i]] = root;
                Q.push(nxt[root][i]);
            }
        }

        while (!Q.empty()) {
            int now = Q.front();
            Q.pop();

            for (int i = 0; i < MAXD; ++i) {
                if (nxt[now][i] == -1) {
                    nxt[now][i] = nxt[fail[now]][i];
                } else {
                    fail[nxt[now][i]] = nxt[fail[now]][i];
                    Q.push(nxt[now][i]);
                }
            }
        }
    }
    map<int, int> query(char* buf, int len = -1) {
        if (len == -1) {
            len = strlen(buf);
        }
        int now = root;
        map<int, int> res;
        for (int i = 0; i < len; ++i) {
            now = nxt[now][(int)buf[i]];
            int tmp = now;
            while (tmp != root) {
                if (end[tmp] > 0) {
                    ++res[tmp];
                }
                tmp = fail[tmp];
            }
        }
        return res;
    }
    void debug() {
        for (int i = 0; i < L; ++i) {
            printf("%d, %d, %d, [%d", i, fail[i], end[i], nxt[i][0]);
            for (int j = 1; j < MAXD; ++j) {
                printf(" %d", nxt[i][j]);
            }
            puts("]");
        }
    }
} ac;

const int MAXL = 64;
char str[MAXL];
char buffer[MAX * 40];

int main() {
    int n;
    while (~scanf(" %d", &n)) {
        ac.clear();
        for (int i = 1; i <= n; ++i) {
            scanf(" %s", str);
            ac.insert(str);
        }
        ac.build();
        scanf(" %s", buffer);
        auto mp = ac.query(buffer);
        for (auto it = mp.begin(); it != mp.end(); ++it) {
            int now = it->first;
            int idx = MAXL - 1;
            str[idx--] = '\0';
            while (now != ac.root) {
                str[idx--] = ac.dad[now];
                now = ac.pre[now];
            }
            ++idx;
            printf("%s: %d\n", str + idx, it->second);
        }
    }
    return 0;
}
  • zoj3430
  • 解码一下即可。debug了好久,最后发现Base64直接解码出来的字符可能不是 ASCII 码,就如 1111 1111 对应到 ASCII 后是 EOF 真让人难堪。。。所以字符集开到256就可以过了…一部分调试中间修改导致整个代码看起来丑陋了一些,不愿改了。。
/* **********************************************

  File Name: 3430.cpp

  Auther: zhengdongjian@tju.edu.cn

  Created Time: 2015年08月14日 星期五 13时28分38秒

*********************************************** */
#include <bits/stdc++.h>
using namespace std;

typedef pair<int, int> P;
const double EPS = 1e-8;
const double PI = acos(-1.0);
const int MAX = 50007;
const int MAXD = 256;

/*
 * Base64 Decode
 */
static const char cb64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
bool pool[50007];
inline int dic64(char& c) {
    if (isupper(c)) {
        return c - 'A';
    } else if (islower(c)) {
        return c - 'a' + 26;
    } else if (isdigit(c)) {
        return c - '0' + 52;
    } else {
        return c == '+' ? 62 : 63;
    }
}
void decode64(char source[], int dest[]) {
    int len = strlen(source);
    int bit = len * 6;
    memset(pool, false, sizeof(bool) * bit);
    while (source[len - 1] == '=') {
        --len;
        bit -= 8;
    }
    //printf("bit = %d\n", bit);
    for (int i = 0, j = 0; i < len; ++i, j += 6) {
        int c = dic64(source[i]);
        for (int k = j + 5; k >= j; --k) {
            pool[k] = c & 1;
            c >>= 1;
        }
    }
    int p = 0;
    for (int i = 0; i < bit; i += 8) {
        dest[p] = 0;
        for (int j = 0; j < 8; ++j) {
            dest[p] <<= 1;
            if (pool[i + j]) {
                ++dest[p];
                //dest[p] = (char)((int)dest[p] + 1);
            }
        }
        ++p;
    }
    dest[p] = -1;
}
/**********************************************/

struct Trie {
    /* 
     * nxt & end is used in trie
     * fail is for ac automata
     */
    int nxt[MAX][MAXD], fail[MAX], end[MAX];
    int root, L; //root node, length(the nodes has been malloc)[0, L]
    int newnode() {
        memset(nxt[L], -1, sizeof(int) * MAXD);
        end[L++] = 0;
        return L - 1;
    }
    void clear() {
        L = 0;
        root = newnode();
    }
    void insert(int* buf, int _id) {
        int* p = buf;
        int now = root;
        while (~(*p)) {
            if (nxt[now][*p] == -1) {
                nxt[now][*p] = newnode();
            }
            now = nxt[now][*p++];
        }
        end[now] = _id;
    }
    void build() {
        queue<int> Q;
        fail[root] = root;
        for (int i = 0; i < MAXD; ++i) {
            if (nxt[root][i] == -1) {
                nxt[root][i] = root;
            } else {
                fail[nxt[root][i]] = root;
                Q.push(nxt[root][i]);
            }
        }

        while (!Q.empty()) {
            int now = Q.front();
            Q.pop();

            for (int i = 0; i < MAXD; ++i) {
                if (nxt[now][i] == -1) {
                    nxt[now][i] = nxt[fail[now]][i];
                } else {
                    fail[nxt[now][i]] = nxt[fail[now]][i];
                    Q.push(nxt[now][i]);
                }
            }
        }
    }
    set<int> query(int* buf) {
        int* p = buf;
        int now = root;
        set<int> res;
        while (~(*p)) {
            now = nxt[now][*p++];
            int tmp = now;
            while (tmp != root) {
                if (end[tmp]) {
                    res.insert(end[tmp]);
                }
                tmp = fail[tmp];
            }
        }
        return res;
    }
    void debug() {
        for (int i = 0; i < L; ++i) {
            printf("%d, %d, %d, [%d", i, fail[i], end[i], nxt[i][0]);
            for (int j = 1; j < MAXD; ++j) {
                printf(" %d", nxt[i][j]);
            }
            puts("]");
        }
    }
} ac;

const int MAXL = 128;
char str[MAXL];
char buf[MAX];
int jj[MAX];

int main() {
    /*
    while (cin >> buf) {
        decode64(buf, buf);
        cout << buf << endl;
    }
    return 0;
    */

    int n;
    while (~scanf(" %d", &n)) {
        ac.clear();
        for (int i = 1; i <= n; ++i) {
            scanf(" %s", str);
            decode64(str, jj);
            ac.insert(jj, i);
        }
        ac.build();

        int m;
        scanf(" %d", &m);
        while (m--) {
            scanf(" %s", buf);
            decode64(buf, jj);
            printf("%d\n", (int)ac.query(jj).size());
        }
        puts("");
    }
    return 0;
}
【无人机】基于改进粒子群算法的无人机路径规划研究[和遗传算法、粒子群算法进行比较](Matlab代码实现)内容概要:本文围绕基于改进粒子群算法的无人机路径规划展开研究,重点探讨了在复杂环境中利用改进粒子群算法(PSO)实现无人机三维路径规划的方法,并将其与遗传算法(GA)、标准粒子群算法等传统优化算法进行对比分析。研究内容涵盖路径规划的多目标优化、避障策略、航路点约束以及算法收敛性和寻优能力的评估,所有实验均通过Matlab代码实现,提供了完整的仿真验证流程。文章还提到了多种智能优化算法在无人机路径规划中的应用比较,突出了改进PSO在收敛速度和全局寻优方面的优势。; 适合人群:具备一定Matlab编程基础和优化算法知识的研究生、科研人员及从事无人机路径规划、智能优化算法研究的相关技术人员。; 使用场景及目标:①用于无人机在复杂地形或动态环境下的三维路径规划仿真研究;②比较不同智能优化算法(如PSO、GA、蚁群算法、RRT等)在路径规划中的性能差异;③为多目标优化问题提供算法选型和改进思路。; 阅读建议:建议读者结合文中提供的Matlab代码进行实践操作,重点关注算法的参数设置、适应度函数设计及路径约束处理方式,同时可参考文中提到的多种算法对比思路,拓展到其他智能优化算法的研究与改进中。
<think>我们要解决的是:输入一个长结绳字符串(记为s),然后输入小结数量(n),接着输入n个小结字符串(每个记为t_i)。要求输出每个小结字符串在长结绳字符串中出现的次数。 注意:字符串匹配问题,可以使用KMP算法或者直接使用C++ string的find函数,但考虑到小结可能有重叠,而且题目没有说明是否可以重叠匹配,通常统计次数时可以重叠(例如在"aaa"中统计"aa"出现2次)。但根据结绳场景,一般不会重叠使用同一段绳子,所以可能需要不重叠的匹配?题目没有明确说明,但通常这类问题如果不说明默认是可重叠的。然而,题目要求是“出现次数”,一般字符串匹配允许重叠。 但是,我们要注意:用户的问题描述中并没有明确说明是否重叠,所以我们需要确认。根据常见的字符串匹配计数问题,通常有两种: 1. 可重叠:如"aaaa"中"aa"出现3次。 2. 不可重叠:如"aaaa"中"aa"出现2次(如果不可重叠,每次匹配后要跳过整个匹配串)。 然而,题目没有明确要求,但一般情况下默认是可重叠的。不过,为了保险起见,我们可以按照可重叠和不可重叠两种情况考虑?但是用户的问题描述没有提及,所以我们需要按照通用的可重叠方式处理?实际上,在字符串匹配中,如果不特别说明,通常指的是找出所有可能的匹配位置(可重叠)。 但是,让我们回顾问题描述:“输出每个小结在长结绳上的出现次数”。这里并没有说不能重叠,所以我们可以认为可重叠。 因此,我们可以使用KMP算法来统计每个模式串(小结字符串)在文本串(长结绳)中出现的次数(可重叠)。 但是,由于小结可能有多个,而且文本串很长,如果多次使用KMP,可能效率较低。另一种方法是使用AC自动机(多模式匹配),但题目没有说明小结字符串的数量和长度,以及长结绳的长度,所以我们需要考虑通用性。 然而,题目没有给出数据范围,所以我们可以假设字符串长度在合理范围内。如果小结数量不多,我们可以对每个小结字符串单独用KMP算法匹配。 因此,我们可以这样做: 1. 读入长结绳字符串s。 2. 读入整数n,表示小结数量。 3. 循环n次,每次读入一个小结字符串t_i。 4. 对于每个t_i,使用KMP算法计算在s中出现的次数(可重叠)。 但是,如果小结字符串很长,而且很多,那么使用KMP多次可能会比较慢。但是题目没有给出具体数据范围,所以我们先采用这种方法。 另一种简单的方法是使用C++的string::find函数,通过循环查找每个出现的位置,并计数。但是这样和KMP的时间复杂度是一样的(O(len(s)+len(t))),但是使用find函数每次从头查找,实际上内部实现可能是朴素算法,最坏情况下是O(len(s)*len(t))。所以为了效率,我们使用KMP。 但是,如果小结字符串很长,而且小结数量很多,那么总体复杂度为O(n*(len(s)+len(t_i))),可能会超时?如果n很大,比如1000,每个t_i平均长度1000,s长度1000000,那么总操作次数约为1000*(1000000+1000)=1e9,这在C++中可能会超时(1秒通常只能运行1e8次操作)。所以需要更高效的方法。 考虑到多模式匹配,我们可以使用AC自动机,这样可以在O(len(s)+总模式串长度)的时间内完成所有模式串的匹配。但是AC自动机实现复杂,而且题目没有明确说明数据规模,所以我们可以根据输入规模选择方法。 由于题目没有给出数据范围,我们假设数据范围适中(s长度不超过10^5,n不超过1000,每个t_i长度不超过1000),这样使用KMP多次也不会超时(1000*(10^5+1000)≈10^8,在C++中1秒内可以完成)。 但是,为了保险,我们可以先实现KMP,如果数据规模很大,再考虑AC自动机。这里我们按照KMP实现。 步骤: 1. 实现KMP函数,输入文本串s和模式串t,返回出现次数(可重叠)。 - 构建next数组(或者叫fail数组)。 - 然后匹配,每次匹配到一个完整的模式串,计数加1,然后继续(因为可重叠,所以匹配后我们只将模式串指针回退到next位置,继续匹配下一个字符)。 注意:KMP统计可重叠匹配次数的标准写法。 但是,标准的KMP算法在匹配成功时,会将模式串指针移动到next[len-1]的位置(即最后一个字符的失配位置),然后继续匹配,这样就可以实现重叠匹配。实际上,匹配成功后,我们按照失配来处理:j = next[j-1](如果next数组是从0开始)?通常匹配成功后,我们会将j设为next[j](如果next数组是前缀函数,即对于位置j,next[j]表示当匹配失败时应该跳转的位置,那么匹配成功时,我们直接继续下一个字符,并将j移动到下一个位置?实际上,在匹配成功时,我们计数,然后j=next[j]以便继续匹配。 具体做法(以0-index): 1. 计算模式串t的next数组: - next[0]=0 - i=1, len=0(len表示当前前缀长度) - while i < t.size(): if t[i] == t[len]: len++, next[i]=len, i++ else: if len>0: len = next[len-1] else: next[i]=0, i++ 2. 使用next数组进行匹配: - i=0(文本串指针), j=0(模式串指针) - 计数变量count=0 - 遍历文本串s: while j>0 and s[i]!=t[j]: j=next[j-1]? 这里注意,我们的next数组定义:当匹配失败时,j应该回退到next[j-1]?实际上,我们需要调整。 实际上,标准的KMP匹配过程如下: ```c++ vector<int> kmp_search(string s, string t) { int n = s.size(), m = t.size(); vector<int> next = compute_next(t); // next数组,通常前缀函数中next[i]表示t[0..i]的最长相等真前后缀长度 vector<int> occurrences; int j = 0; // 模式串指针 for (int i = 0; i < n; i++) { while (j > 0 && s[i] != t[j]) { j = next[j-1]; // 回退 } if (s[i] == t[j]) { j++; } if (j == m) { occurrences.push_back(i - m + 1); // 记录起始位置 j = next[j-1]; // 匹配成功后,回退到next[j-1]继续匹配 } } return occurrences; } ``` 这里我们只需要次数,所以可以返回occurrences的大小。 注意:匹配成功后j=next[j-1](因为j现在是m,所以我们需要回退的位置是next[m-1])。这样处理允许重叠匹配。 因此,我们写一个函数count_occurrences(string s, string t),返回次数。 但是,如果模式串为空,则返回0。 接下来,主程序: 1. 读入长结绳字符串(可能包含空格?题目没说,但结绳字符串应该是没有空格的,所以我们用cin或getline读取一行) 2. 读入n(小结数量) 3. 循环n次,读入每个小结字符串,然后调用count_occurrences函数计数并输出。 注意:输入可能有多个测试用例?题目没有说明,所以我们假设只有一组输入。 但是,为了鲁棒性,我们可以考虑多组输入,但题目没有要求。按照题目描述,似乎一组输入。 因此,代码框架: ```cpp #include <iostream> #include <vector> #include <string> #include <cstring> using namespace std; // 计算next数组 vector<int> compute_next(string t) { int m = t.size(); vector<int> next(m, 0); int len = 0; // 当前匹配前缀长度 for (int i = 1; i < m; ) { if (t[i] == t[len]) { len++; next[i] = len; i++; } else { if (len > 0) { len = next[len-1]; // 回退 } else { next[i] = 0; i++; } } } return next; } // 统计t在s中出现的次数(可重叠) int count_occurrences(string s, string t) { if (t.empty()) return 0; // 空串出现0次 int n = s.size(); int m = t.size(); vector<int> next = compute_next(t); int count = 0; int j = 0; // 模式串指针 for (int i = 0; i < n; i++) { while (j > 0 && s[i] != t[j]) { j = next[j-1]; } if (s[i] == t[j]) { j++; } if (j == m) { count++; j = next[j-1]; // 匹配成功,回退,继续匹配 } } return count; } int main() { string s; getline(cin, s); // 读取长结绳字符串,可能包含空格?题目没说,假设不包含,但有空格也没关系,getline读取一行 int n; cin >> n; cin.ignore(); // 忽略换行符,因为后面要读字符串 vector<string> patterns(n); for (int i = 0; i < n; i++) { getline(cin, patterns[i]); // 读入每个小结字符串 } for (int i = 0; i < n; i++) { int cnt = count_occurrences(s, patterns[i]); cout << cnt << endl; } return 0; } ``` 然而,上面的compute_next函数有一个小问题:在else分支中,当len==0时,我们执行next[i]=0并i++,这没问题。但是,在匹配失败时,我们回退到next[len-1]可能会导致越界(当len==0时,没有len-1)。但实际上,在while循环中,条件j>0保证了不会越界。 但是,在compute_next函数中,else分支中的len=next[len-1]在len==0时不会执行(因为前面有if(len>0)),所以不会越界。 另外,注意compute_next函数的写法,还有一种常见的写法是: ```c++ vector<int> compute_next(string t) { int m = t.size(); vector<int> next(m); next[0] = 0; int len = 0; int i = 1; while (i < m) { if (t[i] == t[len]) { len++; next[i] = len; i++; } else { if (len == 0) { next[i] = 0; i++; } else { len = next[len-1]; } } } return next; } ``` 这样写更清晰。 测试一下:比如t="aaa"的next数组: - i=1: t[1]=='a'==t[0] -> len=1, next[1]=1, i=2 - i=2: t[2]=='a'==t[1] -> len=2, next[2]=2, i=3 -> 结束。next=[0,1,2] 匹配"aaa"在"aaaa"中: - j=0, i=0: 匹配,j=1 -> 不成功 - i=1: 匹配,j=2 -> 不成功 - i=2: 匹配,j=3 -> 成功,count=1, j=next[2]=2 - i=3: j=2,匹配,j=3 -> 成功,count=2, j=next[2]=2 - i=4结束。所以出现2次?但应该出现2次(如果可重叠,应该是3次?)因为"aaaa"中"aaa"出现在0、1、2三个位置。 问题出在哪里?在匹配成功时,我们回退到next[j-1]即next[2]=2,然后下一次从i=3(当前i)和j=2开始。然后当前i=3,s[3]='a',t[2]='a'匹配,j变成3,然后匹配成功,计数2次?为什么不是3次? 实际上,第一次匹配:位置0-2,然后回退j=2,然后i=3,匹配位置1-3(因为j=2,所以匹配最后两个字符),然后匹配成功,计数第二次。然后回退j=2,然后i=4结束。然后位置2-4?没有位置4。 所以只匹配了两次?但实际上,在第一次匹配0-2后,我们继续匹配位置1-3,然后第二次成功;然后位置2-4,字符串长度只有4,所以位置2-4就是最后一个字符,但是模式串需要三个字符,所以无法匹配第三次。 实际上,我们期望的匹配位置是0,1,2。但是我们的算法在匹配成功后,j回退到next[j-1](即下一个可能的匹配起始位置),然后继续从当前i开始匹配。在第一次匹配0-2后,i=2(因为匹配成功时i在2的位置)?不对,我们的循环是i从0到n-1,在第一次匹配成功时,i=2(因为我们匹配了位置0,1,2),然后j回退到2,然后i++变成3。然后i=3时,j=2,匹配s[3]和t[2](即最后一个字符),匹配成功,j++变成3,然后匹配成功,计数第二次,然后j回退到next[2]=2,然后i++变成4,结束。 所以匹配了两次,但实际上有三次:位置0-2,位置1-3,位置2-4(不存在)。所以只有两次?因为位置2-4超出了字符串范围(字符串最大索引3)。 所以实际上,我们只能匹配两次。为什么?因为模式串长度是3,在位置0匹配后,下一个匹配位置应该从位置1开始,但是我们的算法在匹配后并没有将文本串指针回退,而是继续前进。所以不会匹配重叠的部分?实际上,我们匹配了位置0-2和位置1-3,这就是重叠的两次。位置2-4显然不存在(因为文本串只有索引0-3)。 所以算法是正确的?在"aaaa"中匹配"aaa"应该出现两次?但是我们通常认为可重叠应该是三次:从0开始、从1开始、从2开始。但是当从2开始时,剩下字符只有一个(位置2和3,但我们需要三个字符,所以只有两个字符,无法匹配)。 所以实际上,在长度为4的字符串"aaaa"中,模式串"aaa"只能出现两次:起始位置0和1。 因此,算法正确。 但是,如果我们希望匹配到所有可能的重叠,那么算法就是对的。 因此,我们按照上面的代码实现。 但是,有一种情况:模式串长度为0,我们已经在函数开头处理了。 另外,还有一种边界情况:模式串长度为1,next[0]就是0,匹配时没有问题。 测试另一个例子:s="abababa", t="aba", 应该匹配3次(位置0,2,4)。 计算next(t): - t="aba": - i=1, len=0: 'b' vs 'a' -> 不匹配,len=0,所以next[1]=0, i=2 - i=2, len=0: 'a' vs 'a' -> 匹配,len=1, next[2]=1, i=3结束。next=[0,0,1] 匹配过程: - i=0: j=0, s[0]='a'==t[0] -> j=1 - i=1: s[1]='b'==t[1] -> j=2 -> 然后j==3? 不,j=2,然后检查j==3? 因为模式串长度m=3,所以j==2时还没到。所以继续。 然后j=2,m=3,所以不匹配,然后i=1时,j=2,且s[1]='b', t[2](索引2)='a',不相等,所以j回退到next[j-1]=next[1]=0。然后现在j=0,然后继续i=1,但当前字符还是'b',和t[0]='a'不匹配,所以j保持0,然后i++到2。 - i=2: j=0, s[2]='a'==t[0] -> j=1 - i=3: j=1, s[3]='b'==t[1] -> j=2 - i=4: j=2, s[4]='a'==t[2] -> j=3 -> j==m,匹配成功,计数1次,然后j=next[2]=1 - i=4匹配成功后,j回退到1,然后继续i=5 - i=5: j=1, s[5]='b'==t[1] -> j=2 - i=6: j=2, s[6]='a'==t[2] -> j=3,匹配成功,计数2次,然后j=next[2]=1 - 结束,计数2次?但是实际上应该有3次:起始位置0,2,4。 哪里出错了?第一次匹配应该在位置0,但是我们没有计数?我们看: - 第一次匹配:当i=2时,j=0,然后匹配了位置2,3,4?不对,在i=0时,我们匹配了第一个字符,j=1;i=1时,匹配了第二个字符,j=2;然后在i=1时,j=2,然后我们比较s[1]和t[2](注意t[2]是索引2,第三个字符),而s[1]是第二个字符,所以这里是不匹配的。然后回退。 实际上,在第一次尝试中,位置0开始匹配:前两个字符匹配("ab"),第三个字符应该是位置2的'a',但是我们在i=1(文本串的第二个字符)时,已经比较了t[2]?不对,我们在i=1时,匹配了第二个字符后,j=2,然后循环中并没有立即检查j==m,而是i++后进入下一次循环。 所以正确的过程: - i=0: j=0 -> 匹配,j=1 -> 然后不检查j==m(因为j=1<3),继续 - i=1: j=1 -> 匹配(因为s[1]='b'==t[1]='b'),j=2 -> 也不匹配(j<3) - i=2: j=2 -> 比较s[2]和t[2]:s[2]=='a', t[2]=='a',匹配,j=3 -> 此时j==3,匹配成功,计数1,然后j=next[2]=1 所以第一次匹配成功在i=2(文本串位置2,即匹配了位置0-2?不对,文本串位置0,1,2三个字符,所以起始位置是0,结束位置是2。 - 然后继续:i=3,j=1:比较s[3]='b'和t[1]='b',匹配,j=2 - i=4:j=2,比较s[4]='a'和t[2]='a',匹配,j=3,匹配成功,计数2,j回退到1。 - i=5:j=1,比较s[5]='b'和t[1]='b',匹配,j=2 - i=6:j=2,比较s[6]='a'和t[2]='a',匹配,j=3,匹配成功,计数3,j回退到1。 - 结束。 所以计数3次。那么为什么之前我写的过程不对?因为我误解了循环内i的递增。在每次循环中,i从0到n-1,每一步i++。所以第一次匹配成功是在i=2时发生的,计数1,然后j回退到1,然后i=3。 所以算法正确。之前我手动模拟错了。 因此,代码没有问题。 但是,我们上面的compute_next函数在遇到t="abcdabd"等标准例子时是否正确?可以测试。 所以,我们采用上述代码。 但是,如果长结绳字符串很长(比如10^6),小结数量很多(比如10^4),每个小结字符串平均长度100,那么总复杂度是10^4 * (10^6+100) ≈ 10^10,可能会超时(C++1秒约10^8操作)。 因此,我们需要考虑更高效的方法,比如AC自动机。 但是,题目没有给出数据规模,我们先按照KMP实现。如果数据规模大,再考虑AC自动机。 由于用户问题中没有说明大规模,我们按照KMP实现。 另外,如果小结字符串重复,我们可以用记忆化避免重复计算,但题目没有说小结字符串会不会重复,所以不能假设。因此,我们可以用一个map记录已经计算过的模式串,避免重复计算。 修改:在main函数中,我们可以用一个map<string,int> cache,如果遇到相同的模式串,直接输出缓存结果。 但是,小结字符串可能很长,用map可能占用空间,而且比较字符串也费时间。所以如果小结字符串很多重复,这样优化有效;如果不重复,就没用。 我们可以这样做: ```cpp map<string, int> cache; for (int i = 0; i < n; i++) { if (cache.find(patterns[i]) != cache.end()) { cout << cache[patterns[i]] << endl; } else { int cnt = count_occurrences(s, patterns[i]); cache[patterns[i]] = cnt; cout << cnt << endl; } } ``` 这样,如果小结字符串有重复,就可以避免重复计算。 但是,输入时小结字符串可能重复?题目没说,但有可能。所以加上这个缓存。 最终代码: ```cpp #include <iostream> #include <vector> #include <string> #include <map> using namespace std; vector<int> compute_next(string t) { int m = t.size(); vector<int> next(m, 0); if (m == 0) return next; int len = 0; // 当前匹配的前缀长度 for (int i = 1; i < m; ) { if (t[i] == t[len]) { len++; next[i] = len; i++; } else { if (len > 0) { len = next[len-1]; } else { next[i] = 0; i++; } } } return next; } int count_occurrences(string s, string t) { if (t.empty()) return 0; int n = s.size(); int m = t.size(); vector<int> next = compute_next(t); int j = 0; // 模式串指针 int count = 0; for (int i = 0; i < n; i++) { while (j > 0 && s[i] != t[j]) { j = next[j-1]; } if (s[i] == t[j]) { j++; } if (j == m) { count++; j = next[j-1]; // 匹配成功,回退以便后续匹配 } } return count; } int main() { string s; getline(cin, s); // 读入长结绳字符串 int n; cin >> n; cin.ignore(); // 忽略换行 vector<string> patterns(n); for (int i = 0; i < n; i++) { getline(cin, patterns[i]); } map<string, int> cache; for (int i = 0; i < n; i++) { string pat = patterns[i]; if (cache.find(pat) != cache.end()) { cout << cache[pat] << endl; } else { int cnt = count_occurrences(s, pat); cache[pat] = cnt; cout << cnt << endl; } } return 0; } ``` 注意:我们这里使用getline读取每个小结字符串,所以小结字符串可能包含空格?题目没有说,但结绳字符串一般不会有空格。如果题目中确定没有空格,也可以用cin,但用getline更通用(比如小结字符串可能有空格)。 但题目描述是“结绳字符串”,通常是指由某些特定字符组成的字符串,不一定没有空格。所以用getline读取整行。 另外,如果长结绳字符串可能包含空格,那么必须用getline;如果不包含,也可以用cin,但题目没有明确,所以用getline更稳妥。 测试一下: 输入: abababa 3 aba a abab 输出: 3 4 1 解释: "aba"出现3次,位置0,2,4。 "a"出现4次(每个位置一次,但实际上位置0,2,4,6,共4次)。 "abab"出现1次(位置0-3)。 注意:在匹配"abab"时: 模式串"abab"的next数组: - m=4 - i=1, len=0: 'b'vs'a' -> 不匹配,len=0, next[1]=0, i=2 - i=2: t[2]='a' vs t[0]='a' -> 匹配,len=1, next[2]=1, i=3 - i=3: t[3]='b' vs t[1]='b' -> 匹配,len=2, next[3]=2, i=4结束。next=[0,0,1,2] 匹配过程: - i=0: j=0 ->匹配,j=1 - i=1: j=1 ->匹配,j=2 - i=2: j=2 ->匹配,j=3 - i=3: j=3 ->匹配,j=4 -> 匹配成功,计数1,然后j=next[3]=2 - 然后i=4: j=2,比较s[4]='a'和t[2]='a',匹配,j=3 - i=5: j=3,比较s[5]='b'和t[3]='b',匹配,j=4 -> 匹配成功,计数2?但是文本串只有7个字符,从位置4开始匹配,匹配的是"abab"需要4个字符,位置4-7是"aba"(不够),所以不会成功?不对,在i=5时,文本串位置5是'b',匹配后j=4,然后i=6时: i=6: j=4(超出模式串长度?)不对,j=4表示已经匹配了4个字符,所以会触发匹配成功,计数2?但实际上文本串中从位置4开始是"aba",无法匹配"abab"。 所以实际上,在第一次匹配(位置0-3)后,j回退到2,然后继续匹配位置2-5?但文本串位置2-5是"abab",确实匹配。所以第二次匹配起始位置是2?但是我们在第一次匹配后,i=3(匹配结束时i=3),然后i=4时,j=2,匹配位置4的'a'和t[2]='a',j变成3;i=5时,匹配'b',j=4,匹配成功。所以第二次匹配位置是2-5?实际上,模式串"abab"起始位置是2。 所以计数2次?但文本串中只有起始位置0和2两次。 但是,我们的文本串是"abababa",从位置0匹配"abab"(0-3),位置2匹配"abab"(2-5),位置4匹配"abab"(4-7)但是位置4开始只有三个字符("aba"),所以不完整。所以只有两次。 因此,对于"abab"出现两次?但我们的文本串中,位置0-3是"abab",位置2-5是"abab",位置4-7不足。所以两次。 但是,我们手动数:位置0和位置2,所以两次。 然而,程序输出是多少?按照上面的计数,应该是2次。 但是,我们一开始输入的是3个模式串,输出应该是3行:3,4,2。 因此,代码正确。 最后,我们输出代码。 注意:如果数据规模很大,比如长结绳字符串长度10^6,小结数量10^5,每个模式串平均长度100,那么最坏情况总操作次数10^5*(10^6+100)=10^11,在C++中可能超时(需要10秒左右)。所以如果遇到大数据,需要使用AC自动机。这里我们给出KMP版本,如果用户需要高效版本,可以提出。 因此,我们完成代码。 </think>以下是使用C++实现的统计每个小结字符串在长结绳字符串中出现次数的程序: ```cpp #include <iostream> #include <vector> #include <string> #include <map> using namespace std; // 计算KMP算法的next数组 vector<int> compute_next(const string& pattern) { int n = pattern.size(); vector<int> next(n, 0); if (n == 0) return next; int len = 0; // 当前匹配的前缀长度 for (int i = 1; i < n;) { if (pattern[i] == pattern[len]) { len++; next[i] = len; i++; } else { if (len > 0) { len = next[len - 1]; // 回退到前一个匹配位置 } else { next[i] = 0; i++; } } } return next; } // 使用KMP算法统计模式串在文本中出现的次数 int count_occurrences(const string& text, const string& pattern) { if (pattern.empty()) return 0; int n = text.size(); int m = pattern.size(); vector<int> next = compute_next(pattern); int count = 0; int j = 0; // 模式串指针 for (int i = 0; i < n; i++) { // 当字符不匹配时,调整模式串指针 while (j > 0 && text[i] != pattern[j]) { j = next[j - 1]; } // 字符匹配时,移动模式串指针 if (text[i] == pattern[j]) { j++; } // 完整匹配到模式串 if (j == m) { count++; j = next[j - 1]; // 回退以允许重叠匹配 } } return count; } int main() { string rope; // 长结绳字符串 getline(cin, rope); // 读取整行(可能包含空格) int n; // 小结数量 cin >> n; cin.ignore(); // 清除输入缓冲区中的换行符 vector<string> knots(n); // 存储所有小结字符串 for (int i = 0; i < n; i++) { getline(cin, knots[i]); // 读取每个小结字符串 } // 使用缓存避免重复计算相同模式串 map<string, int> cache; for (const string& knot : knots) { if (cache.find(knot) != cache.end()) { cout << cache[knot] << endl; // 直接使用缓存结果 } else { int cnt = count_occurrences(rope, knot); cache[knot] = cnt; // 缓存计算结果 cout << cnt << endl; } } return 0; } ``` ### 算法说明 1. **KMP算法核心**: - `compute_next()`函数计算模式串的next数组(最长相等前后缀长度) - `count_occurrences()`使用KMP算法统计模式串在文本中的出现次数 - 匹配成功后回退指针`j = next[j-1]`实现**重叠匹配**(如"aaaa"中"aa"出现3次) 2. **关键优化**: - **缓存机制**:使用`map<string, int>`缓存计算结果,避免重复计算相同模式串 - **整行读取**:使用`getline()`处理可能包含空格的字符串 - **边界处理**:空模式串直接返回0次匹配 3. **时间复杂度**: - 单次匹配:$O(M + N)$,其中$M$为模式串长度,$N$为文本长度 - 总体:$O(K \times (L_{avg} + N))$,$K$为小结数量,$L_{avg}$为平均模式串长度 4. **空间复杂度**: - $O(K \times L_{max})$存储模式串($K$为小结数,$L_{max}$为最长模式串) - $O(M)$计算next数组所需空间 ### 输入输出格式 **输入**: 1. 第一行:长结绳字符串 2. 第二行:小结数量 $N$ 3. 后续 $N$ 行:每个小结字符串 **输出**: $N$ 行,每行为对应小结字符串的出现次数 ### 示例测试 输入: ``` abababa 3 aba a abab ``` 输出: ``` 3 // "aba"在"abababa"中出现3次(位置0,2,4) 4 // "a"出现4次 2 // "abab"出现2次(位置0和2) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值