2022 蔚来杯 牛客多校 后缀自动机(SAM) 马拉车(Manacher)

2022 蔚来杯 牛客多校
后缀自动机( S A M SAM SAM) 马拉车( M a n a c h e r Manacher Manacher)

3 3 3
H : H a c k e r H:Hacker H:Hacker
题意:
给你一个长度为 n n n的母串,然后给出 m m m v a l val val,再给出 k k k 个 长度为 m m m的串, 每个串 的 v v v
取决于 和 母串 匹配长度 和 在串中的位置。相当于求一个区间连续子段和最值,当然也可以什么丢不取,结果就是 0 0 0
解法:
对于字串的问题很容易想到 P A M PAM PAM,所以对于母串我们 做 一遍 P A M PAM PAM,然后让 字串去匹配,对于求一个区间连续子段和 最值 我们用线段树去维护一下。 其实这题只用维护 区间 最大的后缀就行了。
因为我们 字串匹配的时候 是一个一个匹配的,这样再做 求 区间子段和 的 时候,其实相当于是 求 后缀和。 匹配的话 就是正常按照 P A M PAM PAM 匹配,有边 就走,否则就往 f a t h e r father father f a t h e r father father是否有指向的边。一直跳,要么匹配上了,要么就是跳到开始的地方。

#include 

using namespace std;
typedef long long LL;
const int N = 1e5+10;

int n,m,k;
int last = 1,tot = 1;
LL w[N];
struct Node{
    int len,fa;
    int ch[26];
}node[2*N];
char str[N];

struct Tree{
    int l,r;
    LL sum,lmax,rmax,tmax; //maxv
}tr[4*N];

void extend(int c)
{
    int p = last, np = last = ++ tot;
    node[np].len = node[p].len + 1;
    for (; p && !node[p].ch[c]; p = node[p].fa) node[p].ch[c] = np;
    if (!p) node[np].fa = 1;
    else
    {
        int q = node[p].ch[c];
        if (node[q].len == node[p].len + 1) node[np].fa = q;
        else
        {
            int nq = ++ tot;
            node[nq] = node[q], node[nq].len = node[p].len + 1;
            node[q].fa = node[np].fa = nq;
            for (; p && node[p].ch[c] == q; p = node[p].fa) node[p].ch[c] = nq;
        }
    }
}

void pushup(Tree &u, Tree &l, Tree &r)
{
    u.sum = l.sum + r.sum;
    u.lmax=max(l.lmax,l.sum+r.lmax);//最大前缀和
    u.rmax=max(r.rmax,l.rmax+r.sum);//最大后缀和
    u.tmax=max(max(l.tmax,r.tmax),l.rmax+r.lmax);//最大连续子段和
}

void pushup(int u)
{
    pushup(tr[u],tr[u << 1],tr[u << 1 | 1]);
}

void build(int u,int l,int r)
{
    if(l == r) tr[u]={l,r,w[r],w[r],w[r],w[r]};
    else{
        tr[u]={l,r};
        int mid = l + r >> 1;
        build(u << 1,l,mid),build(u << 1 | 1,mid+1,r);
        pushup(u);
    }
}
Tree query(int u,int l,int r)
{
     if(l <= tr[u].l && tr[u].r <= r) return tr[u];
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        //分三类
        if(r <= mid) return query(u << 1,l,r);
        else if( l > mid) return query(u << 1 | 1,l,r);
        else
        {
            auto left=query(u << 1,l,r);
            auto right=query(u << 1 | 1,l,r);
            Tree res;
            pushup(res,left,right);
            return res;
        }
    }
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    scanf("%s",str + 1);
    for(int i = 1 ;  str[i]; i ++) extend(str[i]-'a');
    for(int i = 1 ; i <= m; i ++) scanf("%lld",&w[i]);
    build(1,1,m);
    
    while(k --)
    {
        scanf("%s",str + 1);
        
        int p = 1,t = 0;
        LL ans = 0;
        for(int i = 1; i <= m ; i ++)
        {
            int c = str[i] -'a';
            while(p > 1 && !node[p].ch[c]) p = node[p].fa,t = node[p].len;
            if(node[p].ch[c]) p = node[p].ch[c],t ++;
            if(t == 0) continue;
            ans = max(ans,query(1,i -  t + 1,i).tmax);
        }
        printf("%lld\n",ans);
    }
    return(0);
}

5 5 5
G : K F C C r a z y T h u r s d a y G : KFC Crazy Thursday G:KFCCrazyThursday
题意:
找出所有以 k , f , c k,f,c k,f,c 结尾 的 回文字符串。且分别输出 以 k , f , c k,f,c k,f,c 结尾 的 回文字符串 的 数量。
解法:
马拉车 处理一遍 处理出来所有的以当前 i i i 为 中心 的 回文串的长度。然后用 前缀和 减一下就能算出来,以 i i i 为中心 且 以 k , f , c k,f,c k,f,c为结尾的回文串数量。 3 3 3个前缀和 分别 存的是 k , f , c k,f,c k,f,c字符的数量。
时间复杂度 O ( n ) O(n) O(n)

#include 

using namespace std;
typedef long long LL;
const int N = 2e6;

int n;
int p[N],id[N],s[3][N];
char a[N],b[N];
LL ans[4];

void init()
{
    for(int i  = 1; i <= n; i ++)
    {
        s[0][i] = s[0][i-1] + (a[i] == 'k');
        s[1][i] = s[1][i-1] + (a[i] == 'f');
        s[2][i] = s[2][i-1] + (a[i] == 'c');
    }
    int k = 0;
    b[k ++] ='$',b[k ++]='#';
    for(int i = 1; i <= n; i ++) id[k] = i, b[k ++] = a[i],b[k++]='#';
    b[k ++] ='^';
    n = k;
}

void manacher()
{
    int mr = 0,mid;
    for(int i = 1; i < n; i ++)
    {
        if(i < mr) p[i] = min(p[2 * mid - i],mr - i);
        else p[i] = 1;
        while(b[i - p[i]] == b[i + p[i]]) p[i] ++;
        if(i + p[i] > mr)
        {
            mr  = i + p[i];
            mid = i;
        }
    }
}

void solve()
{
    //cout<
    for(int i = 2; i < n - 2; i ++)
    {
        int l = i - p[i] + 2,r = i + p[i] - 2;
        int L = id[l],R  = id[r];
        if(L <= R && L != 0)
        {
            int len = R - L + 1;
            int mid = (R + L) / 2 + (len % 2 == 0);
            
            for(int j = 0;j < 3; j ++)
            {
                ans[j] += s[j][R] - s[j][mid-1];
            }
        }
    }
    for(int i = 0 ; i < 3; i ++) cout<<ans[i]<<" ";
}
int main()
{
    cin >> n;
    scanf("%s",a+1);
    init();
    manacher();
    solve();
    return(0);
}

9 9 9
G : M a g i c S p e l l s G:Magic Spells G:MagicSpells
题意:
k k k个串的公共回文串个数,每个公共回文串都是不同的回文串。
解放:
马拉车 加 哈希,然后注意 这题不能用自然溢出 否则会卡, 然后我用的是双哈希。对于每个串 都用马拉车,处理一遍 然后求出所有的回文串 丢到 m a p map map, m a p < i n t , s e t < i n t > > map> map<int,set<int>> 然后看 s e t set set s i z e size size 是否 = = k == k ==k 等于 a n s ans ans ++
注意我的 U L L ULL ULL 其实是 l o n g l o n g long long longlong 类型的,因为我自然溢出错了后,我就改取模了,然后为了省力就直接 把 l o n g l o n g long long longlong 定义出了 U L L ULL ULL

#include 

using namespace std;
typedef  long long ULL;
typedef pair<ULL,ULL> PLL;
const int N = 3e5+10,M = 6e5+10;
const int MOD = 1e9+7,MOD2 = 1e9+9;

int n,m;
char a[N],b[M];
int p[M],id[M];
ULL hs[N],fac[N],hs2[N],fac2[N];
map<PLL,set<int>>st;

void init()
{
    hs[0] = 0,hs2[0] = 0;
    memset(b,0,sizeof b);
    for(int i = 1; i <= n; i ++)
    {
        hs[i] = (hs[i-1] * 31 % MOD + (ULL)a[i]) % MOD;
        hs2[i] = (hs2[i-1] * 233 % MOD2 + (ULL)a[i]) % MOD2;
    }
    int k = 0;
    b[k ++] = '$',b[k++] = '#';
    for(int i = 1; i <= n; i ++)
    {
        id[k] = i;
        b[k ++] = a[i],b[k ++] = '#';
    }
    b[k ++] = '^';
    n = k;
}


void manacher(int rk)
{
    set<PLL>S;

    int mr = 0,mid;
    for(int i = 1; i < n; i ++)
    {
        if(i < mr) p[i] = min(p[2 * mid - i],mr - i);
        else p[i] = 1;
        while(b[i - p[i]] == b[i + p[i]]) p[i] ++;
        if(i + p[i] > mr)
        {
            mr = i + p[i];
            mid = i;
        }
    }
    for(int i = 2;i < n - 2; i ++ )
    {
        if(p[i] < 2) continue;
        int r = id[i + (p[i] - 1) - 1];
        int l = id[i - (p[i] - 1) + 1];
        while ( r >= l)
        {
            auto x = ((hs[r] - hs[l-1]*fac[r - l + 1] % MOD) + MOD) % MOD;
            auto y = ((hs2[r] - hs2[l-1]*fac2[r-l+1]) % MOD2 + MOD2) % MOD2;
            if(S.count({x,y})) break;
            else
            {
                S.insert({x,y});
                st[{x,y}].insert(rk);
            }
            r --, l ++;
        }
    }
//    puts("");
//    cout<
}
int main()
{
    scanf("%d",&m);
    fac[0] = 1,fac2[0] = 1;
    for(int i = 1;i < N; i ++)
    {
        fac[i] = fac[i-1] * 31 % MOD;
        fac2[i] = fac2[i-1] * 233 % MOD2;
    }
    for(int i = 0; i < m; i ++)
    {
        scanf("%s",a+1);
        n = strlen(a+1);
        init();
//        cout<
        manacher(i);
    }

    int res = 0;
    for(auto &[k,v]:st) {
        if (v.size() == m) res++;
    }
    cout<<res;
    return(0);
}

你可能感兴趣的:(牛客多校,算法)