cf edu152 C. Binary String Copying(字符串双哈希)

文章介绍了如何利用字符串哈希和双哈希技术解决Codeforces竞赛中的一个问题。具体来说,给定一个二进制字符串,需要在复制多份后对每份进行区间排序操作,并统计不同副本的数量。通过预处理哈希值,可以高效地计算排序后的字符串哈希,从而快速判断字符串是否相同。
摘要由CSDN通过智能技术生成

cf edu152 C. Binary String Copying(字符串双哈希)

题目链接:https://codeforces.com/contest/1849/problem/C

题目大意

给定一个01字符串,长度为n,拷贝m份,对每一份进行相应操作:将 [ L i , R i ] [L_i,R_i] [Li,Ri]的字符排序,实际就是调整为0在前1在后,问得到的m个副本有多少个不同串。数据范围:n和m都是2e5。

思路

字符串哈希。预处理连续0串和1串的哈希值。

字符串 s 1 s_1 s1 s 2 s_2 s2拼接得到的字符串 s 1 s 2 s_1s_2 s1s2的哈希值为 f ( s 1 s 2 ) = f ( s 1 ) ∗ b a s e ∣ s ∣ + f ( s 2 ) f(s_1s_2)=f(s_1)*\mathop{base}^{\left| s \right|}+f(s_2) f(s1s2)=f(s1)bases+f(s2)

int merge(ll h1, ll h2, int n) {
    return (h1 * bofs[n] % M + h2) % M;
}

逆过来,也可以求某个子串的哈希值,将基的幂次预处理可以 O ( 1 ) O(1) O(1)求得。

int Slice(int l, int r) {
    return (ha[r] - ((ll)ha[l - 1] * bofs[r - l + 1] % M) + M) % M;
}

之后,我们可以 O ( 1 ) O(1) O(1)去求某个字符串的哈希值了。比如对10001100,操作区间 [ 3 , 7 ] [3,7] [3,7],可以通过拼接10,000,11,0四个串的哈希值得到。将这些哈希值放入set中统计。这个方法可以拓展到由任意字符构成的字符串,不过编码比较复杂,要取模。同时单哈希还会被卡,要用两个基做双哈希才行。

正解:对于每个位置 i i i,记录往左第一个0的位置 l i l_i li,和往右第一个1的位置 r i r_i ri,操作区间 [ x , y ] [x,y] [x,y]等价于操作区间 [ l x , r y ] [l_x,r_y] [lx,ry],因此通过操作 [ l x , r y ] [l_x,r_y] [lx,ry]可以标识一个拷贝串,直接将数对放进集合中统计就可以了。

完整代码

写得比较丑陋。

//
// Created by zkr on 2023/7/27.
//

#include<bits/stdc++.h>
using namespace std;
using ll = long long;

const int M = 1e9 + 7;
const int B1 = 29, B2 = 31;

int bofs1[200005], bofs2[200005];   // 先将基预处理出来
int ha1[200005], ha01[200005], ha11[200005];
int ha2[200005], ha02[200005], ha12[200005];
int Slice1(int l, int r) {
    return (ha1[r] - ((ll)ha1[l - 1] * bofs1[r - l + 1] % M) + M) % M;
}
int Slice2(int l, int r) {
    return (ha2[r] - ((ll)ha2[l - 1] * bofs2[r - l + 1] % M) + M) % M;
}
int merge1(ll h1, ll h2, int n) {
    return (h1 * bofs1[n] % M + h2) % M;
}
int merge2(ll h1, ll h2, int n) {
    return (h1 * bofs2[n] % M + h2) % M;
}
void solve() {
    // 2e5进行2e5次排序
    // 统计每个区间的0和1,直接排序构造该串,插入哈希表中
    // 可以O(1)获取子串的哈希
    int n, m;
    cin >> n >> m;
    string s; cin >> s;

    for (int i = 1; i <= n; i++) {
        ha1[i] = ((ll)ha1[i - 1] * B1 + s[i - 1]) % M;
    }   // [1,n]
    for (int i = 1; i <= n; i++) {
        ha2[i] = ((ll)ha2[i - 1] * B2 + s[i - 1]) % M;
    }   // [1,n]
    vector<int> cnt(n + 1);
    for (int i = 1; i <= n; i++) {
        if (s[i - 1] == '1') cnt[i] = cnt[i - 1] + 1;
        else cnt[i] = cnt[i - 1];
    }   // [1,n]
    set<pair<int, int>> st;
    for (int i = 0; i < m; i++) {
        int x, y;
        cin >> x >> y;
        int sum = y - x + 1;
        int cnt1 = cnt[y] - cnt[x - 1];     // 0011 x=1 y=3

        int cur1 = Slice1(y + 1, n);
        if (cnt1 >= 1) cur1 = merge1(ha11[cnt1], cur1, n - y);
        if (sum - cnt1 >= 1) cur1 = merge1(ha01[sum - cnt1], cur1, n - y + cnt1);
        cur1 = merge1(ha1[x - 1], cur1, n - x + 1); // [x, y]
        // 直接拼接得到哈希值

        int cur2 = Slice2(y + 1, n);
        if (cnt1 >= 1) cur2 = merge2(ha12[cnt1], cur2, n - y);
        if (sum - cnt1 >= 1) cur2 = merge2(ha02[sum - cnt1], cur2, n - y + cnt1);
        cur2 = merge2(ha2[x - 1], cur2, n - x + 1); // [x, y]

        st.insert({cur1, cur2});
    }
    cout << st.size() << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    ha01[0] = 0, ha11[0] = 0; // [1,n]
    bofs1[0] = 1;
    for (int i = 1; i <= 2e5; ++i) {
        bofs1[i] = ((ll)bofs1[i - 1] * B1) % M;
        ha01[i] = ((ll)ha01[i - 1] * B1 + '0') % M;
        ha11[i] = ((ll)ha11[i - 1] * B1 + '1') % M;
    }
    ha02[0] = 0, ha12[0] = 0; // [1,n]
    bofs2[0] = 1;
    for (int i = 1; i <= 2e5; ++i) {
        bofs2[i] = ((ll)bofs2[i - 1] * B2) % M;
        ha02[i] = ((ll)ha02[i - 1] * B2 + '0') % M;
        ha12[i] = ((ll)ha12[i - 1] * B2 + '1') % M;
    }
    int t; cin >> t;
    while (t--)
    solve();
    return 0;
}

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

u小鬼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值