Codeforces Good Bye 2020 补题记录

本文介绍了如何处理字符串子串计数问题,涉及动态规划和字符串hash技巧。通过预处理前缀和优化,将复杂度降低,实现高效求解。文章还分享了在实现过程中遇到的调试细节,如数组越界和初始化问题。

系列文章目录

前言

我是傻逼

G. Song of the Sirens

给定n和q,字符串s0和t,t的长度为n
s i s_i si满足 s i + 1 = s i + t i + s i s_{i+1} =s_i+t_i+s_i si+1=si+ti+si,加号表示字符串相接
q次询问,每次询问给出一个字符串 w w w,求 w w w作为子串在 s i s_i si中的出现次数

f ( i , w ) f(i,w) f(i,w) w w w s i s_i si中的出现次数, g ( i , w ) g(i,w) g(i,w) w w w s i s_i si中经过字符串中点 t i − 1 t_{i-1} ti1的出现次数
f ( i , w ) = 2 ∗ f ( i − 1 , w ) + g ( i , w ) f(i,w)=2*f(i-1,w)+g(i,w) f(i,w)=2f(i1,w)+g(i,w)
暴力构造出第一个长度大于等于 ∣ w ∣ |w| w s i d s_{id} sid
就能用字符串hash求 f ( i d , w ) f(id,w) f(id,w) 用来递推了
f f f的公式可以被改写为 f ( i , w ) = 2 k − i d ∗ f ( i d , w ) + ∑ i = i d + 1 k g ( i , w ) ∗ 2 k − i f(i,w)=2^{k-id}*f(id,w)+\sum_{i=id+1}^{k}g(i,w)*2^{k-i} f(i,w)=2kidf(id,w)+i=id+1kg(i,w)2ki
注意到如果 t i = t j t_i=t_j ti=tj i , j > i d i,j>id i,j>id那么 g ( i , w ) = g ( j , w ) g(i,w)=g(j,w) g(i,w)=g(j,w)
这样就可以把g用前缀和优化掉
那么我们就改写一下g
原本没有用前缀和的时候,我们是通过hash求出 w w w在字符串 s i d + c + s i d s_{id}+c+s_{id} sid+c+sid中的出现次数
记为 h [ c ] h[c] h[c]
然后求 ∑ i = i d + 1 k h [ t [ i − 1 ] ] ∗ 2 k − i \sum_{i=id+1}^{k}h[t[i-1]]*2^{k-i} i=id+1kh[t[i1]]2ki
这个可以改写为 ∑ c = 0 25 h [ c ] ∑ i = i d k − 1 [ t [ i − 1 ] = c ] × 2 n − i 2 n − k \frac{\sum_{c=0}^{25}h[c]\sum_{i=id}^{k-1}[t[i-1]=c]\times2^{n-i}}{2^{n-k}} 2nkc=025h[c]i=idk1[t[i1]=c]×2ni
于是后面那个 ∑ \sum 就可以在 O ( n ∗ 26 ) O(n*26) O(n26)时间内预处理
∑ j = 1 i [ t [ j − 1 ] = c ] × 2 n − i = s u m [ c ] [ i ] \sum_{j=1}^{i}[t[j-1]=c]\times2^{n-i}=sum[c][i] j=1i[t[j1]=c]×2ni=sum[c][i]

因为各种小问题调了两天
数组越界 初始化不完整 等等
刷了两页cf提交记录hhhhh
我是傻逼

#include<bits/stdc++.h>
using namespace  std;
#define ull unsigned long long
#define ll long long
const int maxn = 1e5 + 7, maxm = 1048576*4+500;
const ll md = 1e9 + 7;
ull B = 233;
int n, q, k;
string S, t, w;
ll sum[26][maxn], pw[maxn];
ull hpw[maxm], hs[maxm], hsw[maxm];
ll h[26];
vector<string> s;
void read(string &p) {
	static char input[maxm];
	scanf("%s", input);
	p = input;
}
ull calc(int l, int r, ull *in) {    
    if (l == 0) return in[r];
    return in[r]-in[l-1]*hpw[r-l+1];
}
ll ksm(ll a, ll b) {
    ll res = 1ll;
    a %= md;
    while (b) {
        if (b & 1ll) 
            res = res * a % md;
        a = a * a % md;
        b >>= 1ll;
    }
    return res % md;
}
void init() {//预处理O(n)
    pw[0] = hpw[0] = 1;
    for (int i = 1; i <= n; i++) {
        pw[i] = pw[i-1] * 2ll % md;
        hpw[i] = hpw[i-1] * B;
    }
    for (int i = n + 1; i < maxm; i++) hpw[i] = hpw[i-1] * B ;
    for (int i = 0; i < 26; i++) {
        sum[i][1] = pw[n-1]*(t[0] == i + 'a') % md;
        for (int j = 1; j < n; j++) {
            sum[i][j+1] = (sum[i][j] + pw[n-j-1]*(t[j] == i + 'a')) % md;
        }
    }
}
ll sol(int k, string &w) {
    //sum w <= 1e6
    int id = 0, m = w.size();
    s.clear();
    s.push_back(S);
    while (s[id].length() < m) {
        s.push_back(s[id]+t[id]+s[id]);
        id++;
        if (id > k) return 0;
    }
    //if (q == 1)
    //  printf("id == %d k == %d m == %d ", id, k, m);
    hsw[0] = w[0]-'a'; 
    for (int i = 1; i < m; i++) 
        hsw[i] = hsw[i-1] * B + w[i]-'a';
    hs[0] = s[id][0]-'a';
    for (int i = 1; i < s[id].size(); i++) 
        hs[i] = hs[i-1] * B + s[id][i]-'a';
    ll cntf = 0;
    for (int i = m-1; i < s[id].size(); i++) {
        cntf += (hsw[m - 1] == calc(i-m+1, i, hs));
    }
    //if (q==1)
    //   printf("cur == %d m == %d\n", s[id].length(), m);
    ll ans = cntf * pw[k-id] % md;
    for (int i = 0; i < 26; i++) {
        string tmp = s[id] + (char)(i + 'a') + s[id];
        int cur = tmp.length() / 2;
        hs[cur-m] = tmp[cur-m]-'a';
        for (int j = cur-m+1; j < cur + m; j++)
            hs[j] = hs[j-1] * B + tmp[j] - 'a';
        h[i] = 0;
        for (int j = cur; j < cur + m; j++) {
            h[i] += (calc(j-m+1,j,hs) == hsw[m-1]);
            h[i] %= md;
        } 
    }
    ll ans2 = 0;
    for (int i = 0; i < 26; i++) {
        ans2 = (ans2 + h[i] * (sum[i][k] - sum[i][id] + md) % md) % md;
    }
    ans2 = ans2 * ksm(pw[n-k], md-2) % md;
    //if (q == 1) return 683796556;
    return (ans + ans2) % md;
}
int main() {
    cin >> n >> q;
    read(S); read(t);
    init();
    int tt = q;
    while (tt--) {
        cin >> k;
        read(w);
        cout << sol(k, w) << "\n";
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值