2021CCPC女生专场 F. 地图压缩 子串hash+二维KMP

本文介绍了如何通过哈希技术降低算法复杂度,从O(n^2q)提升到O(n^2+nq),解决Codeforces竞赛中的问题F。作者详细阐述了进制哈希和子串哈希的概念,并给出了C++代码实现。重点在于使用子串哈希快速计算给定矩形的最小循环节,适用于多次询问场景。

https://codeforces.com/gym/103389/problem/F
做这个题之前要做一下 p o j 2185 poj2185 poj2185,这两个题很像,但是这个题有多次询问,所以如果每次都去计算一次所给矩形的最小循环节,如果对字符串进行逐个字符的比较,那么复杂度是 O ( n 2 q ) O(n^2q) O(n2q),不行,所以想到哈希,使用子串哈希的方法,使用字符串的哈希值来进行 k m p kmp kmp,这样复杂度就降到了 O ( n 2 + n q ) O(n^2+nq) O(n2+nq)

  • 进制 h a s h hash hash,如果设原来的字符串为 s s s,如果字符串都是小写字母,那么有 h a s h [ i ] = h a s h [ i − 1 ] × b a s e + s [ i ] − ′ a ′ hash[i]=hash[i-1]\times base + s[i]-'a' hash[i]=hash[i1]×base+s[i]a p [ i ] = p [ i − 1 ] × b a s e , ( p [ 0 ] = 1 ) p[i]=p[i-1]\times base,(p[0]=1) p[i]=p[i1]×base,(p[0]=1)其中 b a s e base base可取 13131 13131 13131,这里减不减 ′ a ′ 'a' a都行,如果想得到一个子串的 h a s h hash hash值,那么如果子串是 [ l , r ] [l,r] [l,r],那么有子串哈希值为 h a s h [ r ] − h a s h [ l − 1 ] × p [ r − l + 1 ] hash[r]-hash[l-1]\times p[r-l+1] hash[r]hash[l1]×p[rl+1]
#include <bits/stdc++.h>

using namespace std;

typedef unsigned long long ull;
ull base = 13131;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, q;
    cin >> n >> q;
    vector<vector<char> > mp(n + 1, vector<char> (n + 1));
    vector<vector<ull> > row(n + 1, vector<ull>(n + 1)), col(n + 1, vector<ull>(n + 1));
    vector<ull> p(n + 1);
    p[0] = 1;
    for(int i=1;i<=n;i++){
        p[i] = p[i - 1] * base;
        for(int j=1;j<=n;j++){
            cin >> mp[i][j];
            row[i][j] = row[i][j - 1] * base + mp[i][j] - 'a';
            col[i][j] = col[i - 1][j] * base + mp[i][j] - 'a';
        }
    }
    while(q--){
        int x1, x2, y1, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        int res = x2 - x1 + 1;
        vector<int> Next(res + 1);
        vector<ull> a(res + 1);
        for(int i=x1;i<=x2;i++){
            function<ull(int, int)> Get_Row = [&](int l, int r){
                return row[i][r] - row[i][l - 1] * p[r - l + 1];
            };
            a[i - x1] = Get_Row(y1, y2);
        }
        for(int i=1;i<res;i++){
            int j = Next[i];
            while(j && a[i] != a[j]) j = Next[j];
            Next[i + 1] = (a[i] == a[j] ? j + 1 : 0);
        }
        int x = res - Next[res];
        res = y2 - y1 + 1;
        Next.resize(res + 1);
        Next[1] = 0;
        a.resize(res + 1);
        for(int i=y1;i<=y2;i++){
            function<ull(int, int)> Get_Col = [&](int l, int r){
                return col[r][i] - col[l - 1][i] * p[r - l + 1];
            };
            a[i - y1] = Get_Col(x1, x2);
        }
        for(int i=1;i<res;i++){
            int j = Next[i];
            while(j && a[i] != a[j]) j = Next[j];
            Next[i + 1] = (a[i] == a[j] ? j + 1 : 0);
        }
        int y = res - Next[res];
        cout << x * y << '\n';
    }
    return 0;
}

2022.10.6日备注一下:不要用这种自然溢出,如果数据够硬会被生日攻击卡掉,用双模哈希比较稳

2021CCPC女生程序设计竞赛部分题解如下: - **A题公交路线题解**: ```cpp #include <bits/stdc++.h> using namespace std; int main() { int n, x, y, arr[15]={0}; cin >> n >> x >> y; for (int i = 1; i <= n; i++) { cin >> arr[i]; } int m, xx, brr[15]={0}; cin >> m; if(x<y){ for(int i=1;i<=m;i++){ cin>>brr[i]; if(brr[i]!=arr[i+x]){ cout<<"Wrong"<<endl; return 0; } } if(x-1<1||x-m<1){ cout<<"Right"<<endl; return 0; } for(int i=1;i<=m;i++){ if(arr[x-i]!=brr[i]){ cout<<"Right"<<endl; return 0; } } cout<<"Unsure"<<endl; return 0; }else{ for(int i=1;i<=m;i++){ cin>>brr[i]; if(brr[i]!=arr[x-i]){ cout<<"Wrong"<<endl; return 0; } } if(x==n||x+m>n){ cout<<"Right"<<endl; return 0; } for(int i=1;i<=m;i++){ if(arr[x+i]!=brr[i]){ cout<<"Wrong"<<endl; return 0; } } cout<<"Unsure"<<endl; } return 0; } ``` - **C题题解**: 思路是寻找一个最短的 `t`,使得 `t` 不是 `s(l,r)` 的子序列,假设现在位于 `x`,下一步有 `m` 个选择,要使子序列尽可能小,就选择离 `x` 最远的那个字母,直到走出 `r` 为止。问题转化为从 `l` 开始沿着 `_next` 一路往右跳,要跳多少步才能跳到 `> r` 的地方,使用倍增的方法使得 `_next[i][j]` 是 `i` 从 `2^j` 步到达的最远位置。 ```cpp #include<bits/stdc++.h> using namespace std; char s[200100]; int _next[200100][26];//st表 int v[2000100]; int a[26]; int main(){ int m,n; scanf("%d%d",&m,&n); scanf("%s",s+1); for(int i=0;i<=25;i++){ a[i]=n+1; } for(int i=n;i>=0;i--){ for(int j=0;j<m;j++){ v[i]=max(v[i],a[j]); } _next[i][0]=v[i]; if(i)a[s[i]-'a']=i; } for(int j = 1; j < 20; j ++){ for(int i = 0; i <= n; i ++) { int t = _next[i][j - 1]; if(t <= n) _next[i][j] = _next[t][j - 1]; else _next[i][j] = n + 1; } } int q; scanf("%d",&q); while(q--){ int l,r; scanf("%d%d",&l,&r); int ans = 0, now = l - 1; for(int j = 19; ~j; j --) if(_next[now][j] <= r) ans += (1 << j), now = _next[now][j]; printf("%d\n",ans+1); } return 0; } ``` 以上代码分别对应2021CCPC女生程序设计竞赛A题和C题的题解 [^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Clarence Liu

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

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

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

打赏作者

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

抵扣说明:

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

余额充值