2020牛客多校第二场感想

本题解主要是个人使用,若有问题,欢迎指正。

这次就写了两道题的题解(因为我是一个不会时间管理的孩子)A和J

 

A. All with Pairs 

题意:对于两个字符串s和t,定义f(s,t)为满足$s_{1,2,...i}=t_{|t|-i+1,|t|-i+2,...,|t|}$的最大i,若无次i,则f(s,t)=0。现在给n个字符串$s_1,s_2,...,s_n$,计算$\sum_{i=1}^n \sum_{j=1}^n f^2(s_i,s_j) (mod 998244353)$ 。$n \le 10^5, 1 \le \sum|s_i| \le 10^6$

 

判断两个字符串是否相同可以用字符串哈希,那接下来就是如何统计的问题了。

可以先统计f()函数的第二个参数,也就是把每个后缀的哈希值用map统计出来,然后再计算每个前缀的哈希,把map里相同的值的数量加起来就可以了。

但只是这样的话会遇到重复计数的问题。比如aba为后缀,abac为前缀时,i=1,i=3都会被记录,然而我们只是要记录i=3而已。

因此必须减去这部分重复,很容易想到kmp的next数组。这时有两种思路,一种是对每个后缀进行处理,另一种时对每个前缀进行处理。经过思考,还是对前缀进行处理比较方便。

做法是对于一个字符串,用cnt[i]记录下所有字符串的所有后缀中与这一个字符串的第i个前缀相同的个数,然后cnt[next[i]-1]-=cnt[i-1]。

复杂度$O(\sum|s_i|log\sum|s_i|+\sum|s_i|)$

 

代码见下:

//code by cyh
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<unordered_map>
using namespace std;
typedef unsigned long long ull;
  
const int N=100000+10, LEN=1000000+10;
const int MOD=998244353;
const ull BASE=233, K=7;
const ull BASE2=53;
  
int n;
char t[LEN];
vector<char> s[N];
vector<int> ynext[N];
vector<int> cnt[N];
vector<int> tmp;
ull Pow[LEN], Pow2[LEN];
unordered_map<ull,int> M, M2;
  
void init(){
    Pow[0]=Pow2[0]=1;
    for(int i=1; i<LEN; ++i){
        Pow[i]=Pow[i-1]*BASE;
        Pow2[i]=Pow2[i-1]*BASE2;
    }
}
 
void strhash(char t[], ull base, ull k, unordered_map<ull,int>& M){
    int len=strlen(t);
    ull val=0;
    for(int i=0; i<len; ++i){
        val=val*base+t[i]-'a'+k;
        if(!M.count(val)) M[val]=0;
        M[val]++;
    }
}
 
void kmp_next(char t[], vector<int>& ynext){
    int len=strlen(t);
    ynext[0]=-1; int a=-1;
    vector<int> ttmp; ttmp.resize(len); ttmp[0]=-1;
    for(int i=1; i<len; ++i){
        ttmp[i]=++a;
        while(~a && t[i]!=t[a]) a=ttmp[a];
        ynext[i]=a;
    }
}
 
void solve(int w, char t[]){
    int len=strlen(t);
    s[w].resize(len);
    ynext[w].resize(len+1);
    cnt[w].resize(len);
    tmp.resize(len);
    for(int j=0; j<len; ++j) s[w][j]=t[j];
     
    kmp_next(t,ynext[w]);
    for(int i=0, j=len-1; i<j; swap(t[i++],t[j--]));
    strhash(t,BASE,K,M);
    strhash(t,BASE2,K,M2);
}
  
int main(){
//  freopen("data.txt","r",stdin);
//  freopen("my.txt","w",stdout);
    init();
    scanf("%d", &n);
    for(int i=1, len; i<=n; ++i){
        scanf("%s", t);
        solve(i,t);
    }
    ull ans=0;
    for(int i=1; i<=n; ++i){
        ull val=0, val2=0;
        int sz=s[i].size();
        tmp.resize(sz);
        for(int j=0; j<sz; ++j){
            val=val+(s[i][j]-'a'+K)*Pow[j];
            val2=val2+(s[i][j]-'a'+K)*Pow2[j];
            if(!M.count(val)) M[val]=0;
            if(!M2.count(val2)) M2[val2]=0;
            tmp[j]=min(M[val],M2[val2]);
        }
        for(int j=0; j<sz; ++j){
            if(ynext[i][j]>=0) tmp[ynext[i][j]]-=tmp[j];
        }
        for(int j=0; j<sz; ++j){
            ans=(ans+(j+1)*1ull*(j+1)%MOD*tmp[j])%MOD;
        }
    }
    printf("%d\n", ans);
    return 0;
}
A

我的代码写法与上述分析略有不同,不过并没有什么影响。

 

J. Just Shuffle 

题意:有一个排列B,将{1,2,3,...,n}按这个排列置换k次后会成为排列A,现在给出A和k,问B是什么。$1 \le n \le 10^5, 10^8 \le k \le 10^9, k是质数$

 

置换问题嘛,就好像把几个数字串成了一个环,每置换一次就换了一个起点,如果这个环长度为t,那么置换t次后就会恢复原状。

我们可以从A中得到这几个环。假设某个环长度为t,那么说明这几个数的置换k次后成为了A的这部分。我们想求的是它置换b次后的样子,其中b%t=1。我们可以找到k的逆元,此时k*inv[k]%t=1。也就是把这个环的起点向后移动inv[k]次。

复杂度$O(n)$

 

代码嘛~~贴个网址

https://ac.nowcoder.com/acm/contest/5667#submit/%7B%22problemIdFilter%22%3A207152%2C%22statusTypeFilter%22%3A5%7D

基本上都是这个思路。我的由于写之前没看题解,结果过程过于复杂,就不展示出来了,丢人~

 

结语 

这场比赛的标题起得有水平,i了i了。

打这场时我的知识水平和第一场差不多,因为没看第一场题解~

不过还好这场比第一场简单,结果还看得过去(11题只做出4道叫看得过去?一堆ak的好吧qwq)

 

希望下次会好一点吧。

2020.7.31

posted @ 2020-07-31 09:40  white514  阅读(149)  评论(0编辑  收藏  举报