AtCoder Grand Contest 047 B First Second —— 字典树

This way

题意:

现在有n个字符串,现在对于有一种操作:
对于当前的字符串,你可以删掉第一或者第二个字符
对于两个字符串S,T,称他们是可到达的当且仅当S经过若干次操作之后变成T
问你有多少对字符串是可到达的

题解:

很容易可以得到的一个点:
对于某一个串S,它可以到达的串只有对于位置i(0<=i AtCoder Grand Contest 047 B First Second —— 字典树_第1张图片
红色的后缀前面粘上一个分割线之前出现过的字符(包括空)
那么我们先对于每个串从后往前建出字典树,然后对于每个串,从后往前再建一遍,第一次是建nex指针,第二遍是建num数组。
因为如果直接第一遍就建num数组的话,空间会开的很大,所以我们在第二遍的时候,对于存在的位置建num数组。
在建num数组的时候,对于当前串的当前后缀,加上所有这个串在之前出现过的字符,如果这个位置出现过,那么num++:
假设当前串是1356那么num数组就像这个样子的:
AtCoder Grand Contest 047 B First Second —— 字典树_第2张图片
当然每个位置要存在才行。
最后查询的时候只需要查这个串出现的次数即可,当然要减掉它本身。

#include
using namespace std;
#define ll long long
const int N=1e6+5;
int num[N],nex[N][26],sum[N][26],cnt=1;
ll ans;
string s[N/5+5];
void add1(int x){
    int n=s[x].length();
    int now=1;
    for(int i=n-1;~i;i--){
        if(!nex[now][s[x][i]-'a'])
            nex[now][s[x][i]-'a']=++cnt;
        now=nex[now][s[x][i]-'a'];
    }
}
void add2(int x){
    int n=s[x].length();
    int now=1;
    for(int i=0;i<n;i++){
        for(int j=0;j<26;j++){
            if(s[x][i]-'a'==j)sum[i][j]=(i>0?sum[i-1][j]:0)+1;
            else sum[i][j]=(i>0?sum[i-1][j]:0);
        }
    }
    for(int i=n-1;~i;i--){
        for(int j=0;j<26;j++)
            if(sum[i][j]&&nex[now][j])
                num[nex[now][j]]++;
        now=nex[now][s[x][i]-'a'];
    }
}
void finds(int x){
    int n=s[x].length();
    int now=1;
    for(int i=n-1;~i;i--)
        now=nex[now][s[x][i]-'a'];
    ans+=num[now]-1;
}
int main()
{
    cin.tie(0);
    ios::sync_with_stdio(false);
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>s[i];
        add1(i);
    }
    for(int i=1;i<=n;i++)
        add2(i);
    for(int i=1;i<=n;i++)
        finds(i);
    cout<<ans<<'\n';
    return 0;
}

你可能感兴趣的:(想法,字典树)