当前位置: 首页 > news >正文

字符串

进制哈希

定义一个把字符串映射到整数的函数 f,这个函数 f 称为是 hash函数

核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低
小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果typedef unsigned long long ULL;
ULL h[N], p[N]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64// 初始化
p[0] = 1;
for (int i = 1; i <= n; i ++ )
{h[i] = h[i - 1] * P + str[i];p[i] = p[i - 1] * P;
}// 计算子串 str[l ~ r] 的哈希值
ULL get(int l, int r)
{return h[r] - h[l - 1] * p[r - l + 1];
}

BKDRHash


ull BKDRHash(char *s)
{
    ull P = 131, H = 0;     //P = 31、131、1313、13131、131313
    int n = strlen(s);
    
    for (int i = 0; i < n; i++)
        H = H * P + s[i] - 'a' + 1;     // H = H * P + s[i]     //两种方法
        
    // 相当于:
    // while(*s)
    //   H = H * P + ( *s++ );    return H;
}

求字符串区间 [l, r] 的哈希值:
P[i] 表示 Pi 次方

ull get_hash(ull l, ull r)
{return H[r] - H[l - 1] * P [r - l + 1];
}

双值 Hash

typedef unsigned long long ull; 
ull base = 131; 
ull mod1 = 212370440130137957, mod2 = 1e9 + 7; ull get_hash1(string s) 
{ int len = s.size(); ull ans = 0; for (int i = 0; i < len; i++) ans = (ans * base + (ull)s[i]) % mod1; return ans; 
} ull get_hash2(string s) 
{int len = s.size(); ull ans = 0; for (int i = 0; i < len; i++) ans = (ans * base + (ull)s[i]) % mod2; return ans; 
} bool cmp(const string s, const string t) 
{bool f1 = get_hash1(s) != get_hash1(t); bool f2 = get_hash2(s) != get_hash2(t); return f1 || f2; 
}

进制 Hash 与最长回文串

P3501 [POI 2010] ANT-Antisymmetry - 洛谷 | 计算机科学教育新生态 (luogu. com. cn)

#include <bits/stdc++.h>
using namespace std;
#define ull unsigned long long
const int N = 5e5 + 10;
char s[N], t[N];
int n, PP = 131;
long long ans;
ull P[N], f[N], g[N];  // P: 计算 PP 的 i 次方, f: s 的前缀哈希值, g: t 的前缀哈希值void bin_search(int x) // 用二分法寻找以 s[x] 为中心的回文串
{
    int l = 0, r = min(x, n - x);
    
    while (l < r)
    {
        int mid = (l + r + 1) >> 1;
        
        if ((ull)(f[x] - f[x - mid] * P[mid]) == (ull)(g[x + 1] - g[x + 1 + mid] * P[mid]))
            l = mid;
        else
            r = mid - 1;
    }
    ans += l; // 最长回文串的长度为 l ,它内部有 l 个回文串
}int main()
{
    cin >> n;
    scanf("%s", s + 1);
    P[0] = 1;
    for (int i = 1; i <= n; i++)
        s[i] == '1' ? t[i] = '0' : t[i] = '1'; // t是反串
    for (int i = 1; i <= n; i++)
        P[i] = P[i - 1] * PP; // P[i]是PP的i次方
    for (int i = 1; i <= n; i++)
        f[i] = f[i - 1] * PP + s[i]; // 求s所有前缀的哈希值
    for (int i = n; i >= 1; i--)
        g[i] = g[i + 1] * PP + t[i]; // 求t所有前缀的哈希值
    for (int i = 1; i <= n; i++)
        bin_search(i);
    cout << ans << endl;
}

进制哈希与循环节

P4391 [BOI 2009] Radio Transmission 无线传输 - 洛谷 | 计算机科学教育新生态 (luogu. com. cn)

#include <bits/stdc++.h>
using namespace std;
#define ull unsigned long long
const int N = 1e6 + 100;
ull PP = 131;
char s[N];
ull P[N], H[N], n;
ull get_hash(ull l, ull r) // 区间[l, r]的哈希值
{
    return H[r] - H[l - 1] * P[r - l + 1];
}
int main()
{
    P[0] = 1;
    for (int i = 1; i <= N - 1; i++)
        P[i] = P[i - 1] * PP; // 预处理 PP 的 i 次方
    cin >> n;
    scanf("%s", s + 1); // s[0]不用
    
    for (ull i = 1; i <= n; i++)
        H[i] = H[i - 1] * PP + s[i]; // 预处理所有前缀的哈希值
        
    for (ull i = 1; i <= n; i++)
    {
        ull flag = 1;
        ull last = get_hash(1, i);             // 暴力验证区间[l,r]是否为循环节
        for (int j = 1; (j + 1) * i <= n; j++) // 逐个区间判断
        {
            if (get_hash(j * i + 1, (j + 1) * i) != last) // 这一区间是否与第一区间相同
            {
                flag = 0;
                break;
            }
        }
        if (n * i != 0) // 末位多了一小节, 单独判断
        {
            ull len = n % i;
            if (get_hash(1, len) != get_hash(n - len + 1, n))
                flag = 0;
        }
        if (flag)
        {
            cout << (int)i << endl; // 找到了答案, 输出然后退出
            break;
        }
   }
}

平方探测法

平方探测法是一种处理‌ 哈希表 冲突的方法,其基本思想是在发生冲突时,通过计算一个增量来寻找下一个空闲位置。增量的大小是通过计算键值的平方来确定的,具体公式为:next = (hash(key) + i^2) % tableSize,其中 i 是冲突发生的次数,tableSize 是哈希表的长度。

题目描述:字符串关键字的散列映射 (25 分)【详细解析】_包含英文的字符串 散列函数-CSDN博客

void solve()
{
    int n, p;
    unordered_map<string, int> mp1;
    
    cin >> n >> p;
 
    int m[p];
    string s;
    memset(m, -1, sizeof(m));    for (int i = 1; i <= n; i++)
    {
        cin >> s;
        if (mp1.count(s))
        {
            cout << mp1[s];
            continue;
        }
        
        reverse(s.begin(), s.end());
        int sum = 0;
        int cnt = 0;
        
        for (auto j : s)
        {
            sum += (j - 'A') * pow(32, cnt);
            cnt++;
            if (cnt == 3)
                break;
        }
        reverse(s.begin(), s.end());        int key = sum % p;
        //平方探测法
        if (m[key] != -1)
        {
            int a = 1, b = key;
            
            while (m[key] != -1)
            {
                key = (b + a * a) % p;
                if (m[key] == -1)
                    break;
                key = (b - a * a + p) % p;
                a++;
            }
        }
        
        m[key]++;
        mp1[s] = key;
        cout << key;
        if (i != n)
            cout << ' ';
    }
}

KMP

普通 KMP

模板一

// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度
求模式串的Next数组:
for (int i = 2, j = 0; i <= m; i ++ )
{while (j && p[i] != p[j + 1]) j = ne[j];if (p[i] == p[j + 1]) 	j ++;ne[i] = j;
}// 匹配
for (int i = 1, j = 0; i <= n; i ++ )
{while (j && s[i] != p[j + 1]) j = ne[j];if (s[i] == p[j + 1]) j ++;if (j == m){j = ne[j];// 匹配成功后的逻辑}
}

模板二

void get_Next(string s, int ne[])
{int j = 0;ne[0] = 0;for(int i = 1; i < s.size(); i ++){while(j > 0 && s[i] != s[j])	j = ne[j-1];if(s[i] == s[j])	j ++;ne[i] = j;}
}
int kmp(string s, string t)	//这个函数是从s中找到t,如果存在返回t出现的位置,如果不存在返回-1
{if(t.size() == 0)	return 0;get_Next(t, ne);int j = 0;for(int i = 0; i < s.size(); i ++){while(j > 0 && s[i] != t[j])	j = ne[j-1];	if(s[i] == t[j])	j++;if(j == t.size())	return i - t.size() + 1;}return -1;
}

扩展 KMP

const int N=100010;
int ne[N], ex[N];
/*扩展KMP 
* next[i]:t[]的最长公共前缀 
* extend[i]:s[i...n-1]与t[]的最长公共前缀 
*/
void getNext (string s, int ne[])
{int j = 0, k = 1;ne[0] = (int)s.length();while (s[j] == s[j + 1] && j + 1 < (int)s.length())		j++;ne[1] = j;for(int i = 2; i < (int)s.length(); i++){if(ne[i - k] + i < ne[k] + k)ne[i] = ne[i - k];else{j = max(ne[k] + k - i, (int)0);while(i + j < (int)s.length() && s[j] == s[j + i])	j++;ne[i] = j;k = i;}}
}void exKMP(string s, string t, int ne[], int ex[])
{int j = 0, k = 0;getNext(t);while (s[j] == t[j] && j < (int)s.length() && j < (int)t.length())	j++;ex[0] = j;for (int i = 1; i < (int)s.length(); i++){if(ne[i - k] + i < ex[k] + k)ex[i] = ne[i - k];else{j = max(ex[k] + k - i, (int)0);while(i + j < (int)s.length() && j < (int)t.length() && s[j + i] == t[j])	j++;ex[i] = j;k = i;}}
}

Manacher(马拉车算法)

应用于特定场景:查找一个长度为 n 的字符串 S 中的 最长回文子串
P3805 【模板】manacher - 洛谷 | 计算机科学教育新生态

const int N = 11000002;
int n, P[N << 1];
char a[N << 1], S[N << 1];void change()
{
    n = strlen(a);
    int k = 0;
    S[k++] = '$';
    S[k++] = '#';
    for (int i = 0; i < n; i++)
    {
        S[k++] = a[i];
        S[k++] = '#';
    }
    S[k++] = '&';
    n = k;
}void manacher()
{
    int R = 0, C;
    for (int i = 1; i < n; i++)
    {
        if (i < R)	P[i] = min(P[(C << 1) - i], P[C] + C - i);
        else	P[i] = 1;
        while (S[i + P[i]] == S[i - P[i]])	P[i]++;
        if (P[i] + i > R)
        {
            R = P[i] + i;
            C = i;
        }
    }
}int main()
{
    cin >> a;
    change();
    manacher();
    int ans = 1;
    for (int i = 0; i < n; i++)
        ans = max(ans, P[i]);
    cout << ans - 1 << endl;
}
string change(string &str)
{
    string temp = "#";
    for (int i = 0; str[i]; ++i)
        (temp += str[i]) += "#";
        
    return temp;
}int main()
{
    string str;
    cin >> str;
    str = change(str);
    vector<int> r((int)str.length());
    int c = 0, ans = 0;
    for (int i = 1; str[i]; ++i)
    {
        if (c + r[c] > i)	r[i] = min(r[2 * c - i], c + r[c] - i);
        while (i - r[i] >= 0 && str[i - r[i]] == str[i + r[i]])	++r[i];
        --r[i];
        if (i + r[i] > c + r[c])	c = i; // 注意更新C的位置
        ans = max(ans, r[i]);
    }
    cout << ans << endl;    return 0;
}

回文树(回文自动机)

Problem - 5421

#define int long long
const int N = 3e5 + 8;int s[N];struct node
{
    int len, fail, son[26], siz;
    void init(int _len)
    {
        memset(son, 0, sizeof son);
        fail = siz = 0;
        len = _len;
    }
} tree[N];int num, last[2], ans, L, R;void init()
{
    last[0] = last[1] = 0;
    ans = 0;	num = 1;
    L = 1e5 + 8, R = 1e5 + 7;
    tree[0].init(0);
    memset(s, -1, sizeof s);
    tree[1].init(-1);
    tree[0].fail = 1;
}int getfail(int p, int d)
{
    if (d)
        while (s[R - tree[p].len - 1] != s[R])
            p = tree[p].fail;
    else
        while (s[L + tree[p].len + 1] != s[L])
            p = tree[p].fail;
    return p;
}void insert(int x, int d)
{
    if (d)	s[++R] = x;
    else	s[--L] = x;
    
    int fa = getfail(last[d], d);
    int now = tree[fa].son[x];
    
    if (!now)
    {
        now = ++num;
        tree[now].init(tree[fa].len + 2);
        tree[now].fail = tree[getfail(tree[fa].fail, d)].son[x];
        tree[now].siz = tree[tree[now].fail].siz + 1;
        tree[fa].son[x] = now;
    }    last[d] = now;
    if (R - L + 1 == tree[now].len)		last[d ^ 1] = now;
    ans += tree[now].siz;
}signed main()
{
    int op, n;
    while (scanf("%lld", &n) != EOF)
    {
        init();
        while (n--)
        {
            char ch;
            scanf("%lld", &op);
            if (op == 1)	scanf(" %c", &ch), insert(ch - 'a', 0);
            if (op == 2)	scanf(" %c", &ch), insert(ch - 'a', 1);
            if (op == 3)	printf("%lld\n", num - 1);
            if (op == 4)	printf("%lld\n", ans);
        }
    }
}

字典树(Trie、前缀树)

模板一

P2580 于是他错误的点名开始了 - 洛谷 | 计算机科学教育新生态

const int N=800000;
int n, m;
string s;struct node
{
    int cnt;
    int son[26];
    bool repeat;
    node()
    {
        cnt = 0;
        memset(son, false, sizeof son);
        repeat = false;
    }
} trie[N];int num = 0;void insert(string s)
{
    int v;
    int u = 0;
    for (int i = 0; i < s.length(); i++)
    {
        v = s[i] - 'a';
        if (!trie[u].son[v])	trie[u].son[v] = ++num;
        u = trie[u].son[v];
    }
    trie[u].repeat = 1;
}int find(string s)
{
    int v, u = 0, len = s.length();
    for (int i = 0; i < len; i++)
    {
        v = s[i] - 'a';
        if (!trie[u].son[v])	return 3;
        u = trie[u].son[v];
    }
    if (!trie[u].repeat)	return 3;
    if (!trie[u].cnt)
    {
        trie[u].cnt++;
        return 1;
    }
    return 2;
}int main()
{
    cin >> n;
    for (int i = 1; i <= n; ++i)
    {
        cin >> s;
        insert(s);
    }
    cin >> m;
    for (int i = 1; i <= m; ++i)
    {
        cin >> s;
        int p = find(s);
        if (p == 1)			puts("OK");
        else if (p == 2)	puts("REPEAT");
        else if (p == 3)	puts("WRONG");
    }
}

模板二

const int N = 1000050;int trie[N][26], cnt[N], flag[N], idx;
// 0号点既是根节点,又是空节点
// trie[][]存储树中每个节点的子节点
// cnt[]存储以每个节点结尾的单词数量
// flag[]标记是否是该单词的结尾// 插入一个字符串
void insert(string s)
{int p = 0;for (int i = 0; i < s.size(); i++){int x = s[i] - 'a';if (!trie[p][x]) trie[p][x] = ++idx;p = trie[p][x];}cnt[p]++ ;flag[p] = 1;
}// 查询字符串出现的次数
int query(string s)
{int p = 0;for (int i = 0; i < s.size(); i++){int x = s[i] - 'a';if (!trie[p][x]) return 0;p = trie[p][x];}return cnt[p];
}// 前一个单词是后一个单词的前缀,则称词表为一个词链,求最长词链所包括的单词数
int bfs()
{
    int res = 0;
    queue<int> q;
    q.push(0);
    
    while (q.size())
    {
        int now = q.front();
        q.pop();
        for (int i = 0; i < 26; i++)
        {
            if (trie[now][i])	q.push(trie[now][i]);
            cnt[trie[now][i]] = cnt[now];
            if (flag[trie[now][i]])	cnt[trie[now][i]]++;
        }
        if (res < cnt[now])	res = cnt[now];
    }
    
    return res;
}

AC 自动机

后缀树和后缀数组

后缀自动机

http://www.sczhlp.com/news/660.html

相关文章:

  • js基础第四天
  • 同时点亮LED、数码管以及点阵
  • 今日总结
  • docker安装
  • 二进制简史:从理论到芯片
  • Qcom dcvs_epss.c 驱动解析.
  • AirSim+PX4+QGC实现无人机自动避障
  • js基础第五天
  • 简单了解高阻抗(High-Z)
  • 中台建设为什么需要领域驱动设计
  • 不同Linux发行版Node安装指南
  • 虚化引擎游戏解包工具
  • hyper-v安装manjaro虚拟机
  • spring-data-JPA代码审计
  • 小作业 7(5 道不等式练习题)
  • CF2128F Strict Triangle
  • Dubbo
  • AWS上实现超大规模模型训练的近线性扩展
  • 现代Web服务器性能革命:我的Rust框架探索之旅(6906)
  • Hyperlane性能调优秘籍:从毫秒级响应到百万QPS的优化之路(0548)
  • 实时通信协议的Rust实现(4131)
  • Rust生态系统在Web开发中的优势(9336)
  • TCP连接优化的实战经验(3008)
  • 高并发处理的Rust实现方案(6871)
  • 内存使用效率的终极对决:零拷贝技术的实战应用(0581)
  • 实时通信技术深度对比:WebSocket与SSE的最佳实践(4367)
  • Hyperlane框架的高级特性深度解析:从零拷贝到宏系统的完美融合(8284)
  • WebSocket服务端的高效处理(2177)
  • 跨平台Web服务开发的新选择(5907)
  • 异步编程在Web开发中的应用(9589)