2020牛客暑假多校第二场A. All with Pairs

12 篇文章 0 订阅
5 篇文章 0 订阅

链接

点击跳转

题解

首先看到 ∑ ∣ S i ∣ ≤ 1 0 6 \sum |S_i| \le 10^6 Si106,就考虑能不能分成长度大于根号、长度小于根号两种做

为了方便叙述,令 L = ∑ ∣ S i ∣ L=\sum |S_i| L=Si

长度超过 L \sqrt L L 的串的个数不会多于 L \sqrt L L

这部分串可以直接 k m p kmp kmp计算贡献

剩下的串,每个串长度都不超过 L \sqrt L L

建立AC自动机,然后把每个串在上面跑

跑到的那个点,沿着fail往上跳,沿途所有的深度平方之和加起来就是答案…吗

显然有点问题,一个串的不同前缀会被计入答案

AC自动机里面其实是个字典树,而字典树本质上来说是一个用来分类的数据结构

这就是说父亲对应的串的集合,肯定是其子树中的点对应的字符串集合的扩集

因此我可以在沿着fail往上跳的过程中,看看是不是其子树中已经有儿子算过了,如果算过了就要减去那个数量

这个操作可以借助dfs序

代码

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#define iinf 0x3f3f3f3f
#define linf (1ll<<60)
#define eps 1e-8
#define maxn 1000010
#define maxe 1000010
#define cl(x) memset(x,0,sizeof(x))
#define rep(i,a,b) for(i=a;i<=b;i++)
#define drep(i,a,b) for(i=a;i>=b;i--)
#define em(x) emplace(x)
#define emb(x) emplace_back(x)
#define emf(x) emplace_front(x)
#define fi first
#define se second
#define de(x) cerr<<#x<<" = "<<x<<endl
using namespace std;
using namespace __gnu_pbds;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
ll read(ll x=0)
{
    ll c, f(1);
    for(c=getchar();!isdigit(c);c=getchar())if(c=='-')f=-f;
    for(;isdigit(c);c=getchar())x=x*10+c-0x30;
    return f*x;
}
#define mod 998244353ll
struct Graph
{
    int etot, head[maxn], to[maxe], next[maxe], w[maxe];
    void clear(int N)
    {
        for(int i=1;i<=N;i++)head[i]=0;
        etot=0;
    }
    void adde(int a, int b, int c=0){to[++etot]=b;w[etot]=c;next[etot]=head[a];head[a]=etot;}
    #define forp(_,__) for(auto p=__.head[_];p;p=__.next[p])
}G;
struct Easy_Tree
{
    int depth[maxn], dist[maxn], tid[maxn], rtid[maxn], tim, size[maxn], rev[maxn];
    void dfs(int pos, int pre, Graph& G)
    {
        tid[pos]=++tim;
        rev[tid[pos]]=pos;
        size[pos]=1;
        forp(pos,G)if(G.to[p]!=pre)
        {
            depth[G.to[p]]=depth[pos]+1;
            dist[G.to[p]]=dist[pos]+G.w[p];
            dfs(G.to[p],pos,G);
            size[pos]+=size[G.to[p]];
        }
        rtid[pos]=tim;
    }
    void run(Graph& G, int root)
    {
        tim=0;
        depth[root]=1;
        dfs(1,0,G);
    }
}et;
struct ACautomaton
{
    int trie[maxn][26], tot, fail[maxn], cnt[maxn], dep[maxn];
    ll w[maxn];
    void clear()     //clear the arrays
    {
        for(int i=1;i<=tot;i++)cl(trie[i]), fail[i]=cnt[i]=0;
        tot=1;
    }
    int insert(char *r, int len)     //insert a string into trie tree
    {
        auto pos=1;
        for(ll i=1;i<=len;i++)
        {
            pos = trie[pos][r[i]-'a'] ? trie[pos][r[i]-'a'] : trie[pos][r[i]-'a']=++tot;
            cnt[pos]++;
            dep[pos]=i;
        }
        return pos;
    }
    void build()    //build the aca
    {
        queue<int> q;
        int u, v, f;
        q.push(1);
        while(!q.empty())
        {
            u=q.front(); q.pop();
            for(auto i=0;i<26;i++)
                if(trie[u][i])
                {
                    v=trie[u][i];
                    for(f=fail[u];f and !trie[f][i];f=fail[f]);
                    fail[v] = f ? trie[f][i] : 1;
                    //---计算w[v]
                    w[v] = ( w[fail[v]] + (ll)cnt[v]*dep[v]%mod*dep[v] )%mod;
                    ll p=fail[v], dfn=et.tid[v];
                    while(dfn < et.tid[p] or dfn>et.rtid[p])
                        p=fail[p];
                    (w[v] -= (ll)cnt[v]*dep[p]%mod*dep[p])%mod;
                    //---
                    q.push(v);
                }
        }
    }
    int move(int pos, int c)
    {
        c-='a';
        for(;pos and !trie[pos][c];pos=fail[pos]);
        return pos ? trie[pos][c] : 1;
    }
}aca;
struct KMP
{
    int n, next[maxn], t[maxn];
    void build(char *r, int len)
    {
        int i, j=0;
        n=len;
        for(i=1;i<=len;i++)t[i]=r[i];  t[len+1]=0;
        for(i=2;i<=len;i++)
        {
            for(;j and t[j+1]!=t[i];j=next[j]);
            next[i] = t[j+1]==t[i]?++j:0;
        }
    }
    int move(int pos, int x)
    {
        for(;pos and t[pos+1]!=x;pos=next[pos]);
        return t[pos+1]==x ? pos+1 : 0;
    }
}kmp;
char s[maxn];
int pos[maxn], len[maxn];
const ll S=1000;
int main()
{
    int n=read(), i, j, k;
    ll ans=0;
    len[0]=1;
    aca.clear();
    rep(i,1,n)
    {
        pos[i] = pos[i-1] + len[i-1];
        scanf("%s",s+pos[i]);
        len[i]=strlen(s+pos[i]);
        if(len[i]<S)
        {
            aca.insert(s+pos[i]-1,len[i]);
        }
    }
    rep(i,1,aca.tot)rep(j,0,25)
        if(aca.trie[i][j])G.adde(i,aca.trie[i][j]);
    et.run(G,1);
    aca.build();
    rep(i,1,n)
    {
        int p=1;
        rep(j,1,len[i])
        {
            p=aca.move(p,s[pos[i]+j-1]);
        }
        (ans+=aca.w[p])%=mod;
        // de(p);
        // de(aca.w[p]);
    }
    rep(i,1,n)
    {
        if(len[i]>=S)
        {
            kmp.build(s+pos[i]-1,len[i]);
            rep(j,1,n)
            {
                int p = 0;
                rep(k,1,len[j])p=kmp.move(p,s[pos[j]+k-1]);
                (ans+=ll(p)*p)%mod;
            }
        }
    }
    printf("%lld",(ans+mod)%mod);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值