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; }
我的代码写法与上述分析略有不同,不过并没有什么影响。
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