字符串


字符串算法

KMP算法

时间复杂度O(m+n)

主串与子串按位置比对,如果发现某一个位置不匹配,则寻找在子串该位置前的前缀长。

const int maxn = 1000005;
string s1, s2;   //s1为主串,s2为子串
int next[maxn]; //next数组,保存了s1的前后缀交集的最长长度
void make_next() {  //求解next数组
    next[0] = 0;  //首字母不与自己产生匹配
    for (int i = 1, j = 0; i < s2.size(); ++i) {    //字符串自匹配
        while (j > 0 && s2[i] != s2[j]) j = next[j - 1];   //往前回溯
        if (s2[i] == s2[j]) ++j;
        next[i] = j;  //记录前缀数
    }
}
int kmp() {
    for (int i = 0, j = 0; i < s1.size(); ++i) {
        while (j > 0 && s1[i] != s2[j]) j = next[j - 1];   //不同就往前找前缀
        if (s1[i] == s2[j]) ++j;   //如果两者相同,则都向后检验
        if (j == s2.size()) return i - j + 1;
    }
    return -1;  //找不到返回-1
}

字典树

Trie(经典字典树)

将字符串分割成单个字符进行存储,相同前缀的字符串共享前缀。适用于做前缀匹配的问题。

struct node {
    node(char c) {
        sum = end = 0;
        ch = c;
    }
    int sum, end;    //记录前缀出现的次数和单词的次数
    char ch;
    map<char, node*> mp;
};

class Trie {
private:
    node* root;
public:
    Trie() {
        root = new node(' ');
    }
    void insert(string str) {
        node* pos = root;
        for (int i = 0; i < str.size(); ++i) {
            map<char, node*>::iterator it = (pos->mp).find(str[i]);    //查询单字符是否存在
            if (it == (pos->mp).end()) {   //找不到就创建一个节点
                node* tmp = new node(str[i]);
                pos->mp[str[i]] = tmp;
            }
            pos = pos->mp[str[i]];    //移动到下一位
            ++(pos->sum);   //前缀出现次数+1
        }
        ++(pos->end);   //单词次数+1
    }
    int query(string str) {
        node* pos = root;
        //  int ans=0;
        bool flag = true; //默认可以查询到完整的单词
        for (int i = 0; i < str.size(); ++i) {
            map<char, node*>::iterator it = (pos->mp).find(str[i]);    //查询单字符是否存在
            if (it == (pos->mp).end()) {   //查不到
                flag = false;
                break;
            }
            pos = pos->mp[str[i]];    //找得到接着找
            //  ans+=(pos->end);
        }
        if (flag) return (pos->sum);
        else return 0;
    }
};

01字典树

将数据以二进制的方式进行存储,一般用于进行异或运算。存储的时候只需要存储0或1,所以可以将其看作是一棵二叉树

以下是01字典树的数组写法(以最大异或对为例):

const int MX = 100005;
int tot, son[MX * 30][2], a[MX];
void build(int x) { //建树
    int p = 0;
    for (int k = 30; k >= 0; --k) {
        int tmp = (x >> k) & 1;   //按照二进制的方式存储数据
        if (!son[p][tmp]) son[p][tmp] = ++tot; //tot给对应的节点编号
        p = son[p][tmp];  //下一个节点
    }
}
int que(int x) {
    int p = 0, ans = 0;
    for (int k = 30; k >= 0; --k) {
        int tmp = (x >> k) & 1;
        if (son[p][!tmp]) {  //从x的高位开始异或,尽量让高位为1
            p = son[p][!tmp];
            ans |= (1 << k);
        }
        else p = son[p][tmp]; //查询下一个节点
        if (!p) break;
    }
    return ans;
}
int main() {
    int n; cin >> n;
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
        build(a[i]);
    }
    int ans = 0;
    for (int i = 0; i < n; ++i) ans = max(ans, que(a[i]));    //对每一个数都求一次答案
    cout << ans << endl;
    return 0;
}

最长异或路径

最长异或路径的解法是使用01字典树。

问题描述

给定一棵 n 个点的带权树,结点下标从 1 开始到 n。寻找树中找两个结点,求最长的异或路径。异或路径指的是指两个结点之间唯一路径上的所有边权的异或。

输入格式

第一行一个整数 n,表示点数。

接下来 n−1 行,给出 u,v,w ,分别表示树上的 u 点和 v 点有连边,边的权值是 w。

输出格式

一行,一个整数表示答案。

样例
样例输入 #1
4
1 2 3
2 3 4
2 4 6
样例输出 #1
7
解题思路

由异或的性值可得,对于树的任意路径<i, j>的异或值,均可由<root, i>和<root, j>两条路径的异或值异或而成,故可以先使用dfs计算出每一个顶点i到root的路径异或值Di,再将Di存储在数组sum[i]中,最后将问题转化为对sum[]数组求解最大异或数对。

最长异或路径(coding)

接下来使用C++类的写法来写01字典树的最长异或路径问题:

const int maxn = 100005;
int n, u, v, w, ans;
int sum[maxn];  //sum[i]表示第i个节点到root的异或值
bool mark[maxn];    //dfs辅助数组
struct node {
    node(int _v, int _w) {
        vertex = _v, weight = _w;
    }
    int vertex, weight;
};
struct Trienode {    //模仿huffman树的构建方式,左0右1
    Trienode() {
        left = right = NULL;
    }
    Trienode* left;
    Trienode* right;
};
class Trie {
private:
    Trienode* root;
public:
    Trie() {
        root = new Trienode();
    }
    void insert(int x) {
        Trienode* pos = root;
        for (int i = 30; i >= 0; --i) {
            int tmp = (x >> i) & 1;   //取x的最高位
            if (tmp) {   //数位为1,存到右边
                if (pos->right == NULL) pos->right = new Trienode();
                pos = pos->right;
            }
            else {  //数位为0,存到左边
                if (pos->left == NULL) pos->left = new Trienode();
                pos = pos->left;
            }
        }
    }
    int query(int x) {
        int tmpans = 0;
        Trienode* pos = root;
        for (int i = 30; i >= 0; --i) {
            int tmp = (x >> i) & 1;   //取高位
            if (tmp) {   //数位为1,优先贪心0
                if (pos->left != NULL) tmpans |= (1 << i), pos = pos->left;
                else pos = pos->right;
            }
            else {  //数位为0,优先贪心1
                if (pos->right != NULL) tmpans |= (1 << i), pos = pos->right;
                else pos = pos->left;
            }
        }
        return tmpans;
    }
};
vector<node> G[maxn];   //邻接矩阵存树
void dfs(int start, int index) {
    mark[start] = 1;  //标记已访问
    for (int i = 0; i < G[start].size(); ++i) {
        if (!mark[G[start][i]].vertex) {
            sum[G[start][i].vertex] = index ^ (G[start][i].weight); //先利用dfs算出各点到根节点的异或值
            dfs(G[start][i].vertex, sum[G[start][i].vertex]);
        }
    }
}
int main() {
    cin >> n; //顶点数
    for (int i = 1; i <= n - 1; ++i) {
        cin >> u >> v >> w;
        node n1(u, w), n2(v, w);
        G[u].push_back(n2);
        G[v].push_back(n1); //建树
    }
    dfs(1, 0);
    Trie tree;
    for (int i = 1; i <= n; ++i) tree.insert(sum[i]);
    for (int i = 1; i <= n; ++i) ans = max(ans, tree.query(sum[i]));
    cout << ans << endl;
    return 0;
}

文章作者: 热心市民灰灰
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 热心市民灰灰 !
  目录