牛客国庆集训派对Day5 H题 我不爱她

版权声明:转载请说明,欢迎交流! https://blog.csdn.net/qq_39599067/article/details/82951276

(有任何问题欢迎留言或私聊 && 欢迎交流讨论哦

Catalog

Problem:传送门

Portal

 原题目描述在最下面。


Solution:

先放一个群里某大佬的解释:

就是一个串w是a的前缀也是b的后缀
那么border(w)也是a的前缀也是b的后缀
(因为border(w)是w的前缀也是w的后缀)
所以把每个w的贡献记为len(w) - len(border(w))
如果w被匹配了 说明border(w)一定不是最长的 相当于加上len(w)的时候 把不是最长的len(border(w))贡献去掉
最后累加起来剩下的都是最长的


在告诉你一个小秘密:

int ans = 0, t = 0;
for(int i = len; i > 0; ) {
    ans += i - nex[i];
    i = nex[i];
    p[t++] = nex[i];
}
printf("ans = %d\n", ans);///ans 的值和这个字符串的长度一样哦!!!
///只有p数组里存的前缀才是这个字符串的后缀!(如果这句话是错的,望大佬指出!

 讲讲我的理解吧

 如那位大佬所言,若字符串 w w 是所求的巧合值,也就是 w w a a 的后缀,也是 b b 的后缀,同样 b o r d e r ( w ) border(w) 也满足此条件,只不过 l e n ( b o r d e r ( w ) ) < l e n ( w ) len(border(w))<len(w)

 问题在于,你对每两个字符串求 b o r d e r border 时间上肯定 t l e tle 了。怎么解决呢?

 题解上讲的是每个前缀/后缀的贡献记为 l e n ( w ) l e n ( b o r d e r ( w ) ) len(w)-len(border(w))

 然后你把所有的前缀子串贡献记为 l e n ( i ) l e n ( b o r d e r ( i ) ) len(i)-len(border(i)) ,再用 m a p map 存一下每个前缀 o r or 后缀的出现次数。最后遍历一遍所有后缀或前缀,累加贡献即可。

 我上面写了只有 p p 数组里的前缀才会是这个字符串的后缀!而且每个字符串不会有算重复的地方, p p 数组求一遍 k m p kmp f a i l fail 数组就行了。


AC_Code:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <queue>
#include <unordered_map>
#define fi first
#define se second
#define iis std::ios::sync_with_stdio(false)
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<LL, LL> pii;
/*
就是一个串w是a的前缀也是b的后缀
那么border(w)也是a的前缀也是b的后缀
(因为border(w)是w的前缀也是w的后缀)
所以把每个w的贡献记为len(w) - len(border(w))
如果w被匹配了 说明border(w)一定不是最长的 相当于加上len(w)的时候 把不是最长的len(border(w))贡献去掉
最后累加起来剩下的都是最长的
int ans = 0, t = 0;
for(int i = len; i > 0; ) {
    ans += i - nex[i];
    i = nex[i];
    p[t++] = nex[i];
}
printf("ans = %d\n", ans);///ans 的值和这个字符串的长度一样哦!!!
///只有p数组里存的前缀才是这个字符串的后缀!(如果这句话是错的,望大佬指出!
*/
const long long HMOD[] = {2078526727, 2117566807};
const long long BASE[] = {1572872831, 1971536491};
const int MXN = 1e5 + 5;
const int MXT = 1e6 + 5e5 + 6;

char s[MXT];
int fail[MXT];
std::vector<pii> ve;
unordered_map<LL, int> mp;
void get_fail(char *t,int m) {
    fail[0] = -1;
    for(int i = 0,k = -1; i < m;) {
        if(k == -1 || t[i] == t[k]) {
            ++ i; ++ k;
            fail[i] = k;
        }else k = fail[k];
    }
}
int main(int argc, char const *argv[]) {
    LL N = (int)2e9, hh;
    int tim;
    scanf("%d", &tim);
    while(tim--) {
        ve.clear();
        mp.clear();
        int n; scanf("%d", &n);
        for(int i = 0; i < n; ++i) {
            scanf("%s", s);
            int len = strlen(s);
            get_fail(s, len);
            pii tmp = {0, 0};
            for(int i = 0; i < len; ++i) {
                tmp.fi = (tmp.fi*BASE[0] + (s[i]-'a'+1))%HMOD[0];
                tmp.se = (tmp.se*BASE[1] + (s[i]-'a'+1))%HMOD[1];
                hh = tmp.fi*N + tmp.se;
                ve.push_back({hh, i + 1 - fail[i + 1]});
            }
            tmp = {0, 0};
            LL p1 = 1, p2 = 1;
            for(int i = len - 1; i >= 0; --i) {
                tmp.fi = (tmp.fi + (s[i]-'a'+1)*p1)%HMOD[0];
                tmp.se = (tmp.se + (s[i]-'a'+1)*p2)%HMOD[1];
                hh = tmp.fi*N + tmp.se;
                mp[hh] ++;
                p1 = p1 * BASE[0] % HMOD[0];
                p2 = p2 * BASE[1] % HMOD[1];
            }
        }
        int len = ve.size();
        LL ans = 0;
        for(int i = 0; i < len; ++i) {
            ans += mp[ve[i].first]*ve[i].se;
        }
        printf("%lld\n", ans);
    }
    return 0;
}

Problem Description:

终于活成了自己讨厌的样子。

天空仍灿烂,它爱着大海。

你喜欢大海,我爱过你。

世界上充满了巧合。我们把每句话当成一个字符串,我们定义a对b的巧合值为a的最长后缀的长度并且它是恰好是b的前缀,这里的后缀或者前缀包括字符串的本身。
比如字符串“天空仍灿烂她喜欢大海”对“她喜欢大海我不爱她了我爱的只是与她初见时蔚蓝的天空”的巧合值为5,而字符串“她喜欢大海我不爱她了我爱的只是与她初见时蔚蓝的天空”对“天空仍灿烂她喜欢大海”的巧合值为2。
现在给出n个字符串由"ab"构成的字符串s1,s2,…,sn,求出对于所有1≤ i,j≤ n,si对sj的巧合值的和。

e

猜你喜欢

转载自blog.csdn.net/qq_39599067/article/details/82951276
今日推荐