嘘~ 正在从服务器偷取页面 . . .

CF1200E Compress Words 题解


CF1200E Compress Words 题解

题目链接:CF1200E Compress Words

题意:给定一堆字符串,依次插入答案串尾部,每次删掉答案串的后缀 与 待插入串的前缀的最大匹配串

解法一 KMP

这个解法常数比较大

可以想到,暂时把待插入串放到答案串前,然后跑一遍KMP

最后一个值就是新串的最长前后缀,也就是我们要求的最大匹配串的长度

由于直接拼接新串会导致某些时候答案超出原长

因此我们要在它们拼接的地方加上一些非字符集的字符或者乱七八糟的字符

注意不能自带border

而这样的算法还是不行的,因为答案串中有很多字符其实用不到

因此我们只要截取答案串后面的拼接即可

时间复杂度 \(O(\sum |s_i|) = O(n)\)

代码如下

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N (int)(2e6+5)
int n,fail[N],len1,len2;
char ans[N],s[N];
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin >> n;
    for(int i=1; i<=n; i++)
    {
        cin >> s+1;len2=strlen(s+1);
        int minlen=min(len1,len2);
        string str="0";
        for(int i=1; i<=minlen; i++)
            str+=s[i];
        str+="!@#$%^&*";
        for(int i=len1-minlen+1; i<=len1; i++)
            str+=ans[i];
        for(int i=2,j=0; i<=minlen*2+8; i++)
        {
            while(j&&str[i]!=str[j+1])j=fail[j];
            if(str[i]==str[j+1])++j;
            fail[i]=j;
        }
        for(int i=fail[minlen*2+8]+1; i<=len2; i++)
            ans[++len1]=s[i];
    }
    cout << ans+1 << endl;
    return 0;
}

解法二 字符串哈希

这里主要扯扯字符串哈希的东西

虽然之前没怎么研究,一直用的unordered_map<string,int>mp

但是这次就手写个字符串哈希吧

一般字符串哈希有两种写法,这里就按照OI WIKI默认的写法来讲了

定义哈希函数 \(f(s) = \sum\limits_{i=1}^{l}s[i]\times b^{l-i} \mod M\)

一般来讲我们不会只用一个大质数 \(M\) ,而是一堆 qwq

因为这样它的 \(n\) 次比较的错误率会降到 \(1-\left(1-\dfrac{1}{\prod M_i}\right)^n\)

这个数有多小呢?我们假设只有两个大质数 1e9+7,998244353,比较1e6

那么它的值约为 \(0.00000000000100175873\)

这错误的概率还没计算机出故障的几率高吧

因此我们直接忽略不计

那么对于一个字符串 \(\tt{xyz}\),它的值就等于 \((xb^2+yb+z) \mod M\)

类比于一个 \(b\) 进制的数,应该还算比较好理解的

根据这个式子,如果我们要求它的一个子串 \(s[l\dots r]\) 的哈希值怎么办

注意到 \(f(s[l\dots r]) = \sum\limits_{i=l}^{r}s[i]\times b^{r-i} \mod M\)

因此可以得到结论 \(f(s[l\dots r]) = f(s[1\dots r]))-f(s[1\dots l-1])\times b^{r-l+1} \mod M\)

注意这里,由于做了取模操作,\(f(s[l\dots r])\) 的值可能为负数,要注意把它转成正的

这样我们就可以 \(O(n)\) 预处理 \(b\)\(O(1)\) 查询了(也可以就快速幂 \(O(\log n)\) 查询)

好的相信你已经理解了字符串哈希的一些基本东西,下面来做个简单的小例题

好吧就是这题,看它题意

很简单,直接暴力枚举+哈希就OK了,

而朴素的解法,语句 str1==str2 ,它实质上是 \(O(n)\) 级别的

因此我们把朴素解法的 \(O(n^2)\) 就可以压到 \(O(n)\)

代码如下

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N (int)(1e6+5)
#define hash_cnt 2
const int hash_base[hash_cnt]={29,31};
const int hash_mod[hash_cnt]={1000000007,998244353};
struct hash_str
{
    char s[N];
    int len,hsh[hash_cnt][N];
    int pwd[hash_cnt][N];
    
    void init()
    {
        len=0;
        for(int i=0; i<hash_cnt; i++)
        {
            hsh[i][0]=0;
            pwd[i][0]=1;
        }
    }
    hash_str(){init();}
    void add(char ch)
    {
        s[++len]=ch;
        for(int i=0; i<hash_cnt; i++)
        {
            pwd[i][len]=pwd[i][len-1]*hash_base[i]%hash_mod[i];
            hsh[i][len]=(hsh[i][len-1]*hash_base[i]+ch)%hash_mod[i];
        }
    }
    vector<int> gethash(int l,int r)
    {
        vector<int> res(hash_cnt,0);
        for(int i=0; i<hash_cnt; i++)
        {
            int t=(hsh[i][r]-hsh[i][l-1]*pwd[i][r-l+1])%hash_mod[i];
            t=(t+hash_mod[i])%hash_mod[i];
            res[i]=t;
        }
        return res;
    }
}s,t;
bool equal(const vector<int> &vec1,const vector<int> &vec2)
{
    assert(vec1.size()==vec2.size());
    for(int i=0; i<vec1.size(); i++)
        if(vec1[i]!=vec2[i])return 0;
    return 1;
}
int n;
char str[N];
void solve(char *str)
{
    int len=strlen(str+1);
    t.init();
    for(int i=1; i<=len; i++)
        t.add(str[i]);
    int d=1;
    for(int j=min(len,s.len); j>=1; j--)
        if(equal(t.gethash(1,j),s.gethash(s.len-j+1,s.len)))
            {d=j+1;break;}
    for(int i=d; i<=len; i++)
        s.add(str[i]);
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin >> n;
    for(int i=1; i<=n; i++)
    {
        cin >> str+1;
        solve(str);
    }
    cout << s.s+1 << endl;
    return 0;
}

文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录