洛谷 1666 前缀单词 trie树 dp

版权声明:本文为博主原创文章,可以转载但是必须声明版权。 https://blog.csdn.net/forever_shi/article/details/82713210

题目链接
题意:
给你n个字符串,每次选出若干个字符串形成一个集合,问有多少个集合满足集合中的任何一个字符串都不是另外一个字符串的前缀。空集也一定是满足条件的。保证不会出现两个相同的字符串。

题解:
也是NOIP模拟赛出现的题。当时想到了建trie树之后dp(我也忘了是怎么想到的了),但是当时我感觉算互相不为前缀可能不太好算,于是就在考虑用集合总数减去存在互为前缀的集合,结果发现自己dp计数出现了错误,只好交了暴力。听完讲评后研究了一下发现直接做其实也并不难做(又一次暴露出我菜)。
于是我来说一下讲评时给出的做法。首先对所有字符串建出一棵trie树,然后我们可以发现其实trie树上有用的点只有那些作为某个串的末尾被打上标记的点,于是我们根据trie树上祖先后代的关系对这些有用的点建出一棵新树来(仍然保留虚根0号点)。此时我们不难想到,如果新树上某个点被选了,那么合法的集合中它的子树内的所有其他节点都不能被选了;如果某个节点不被选,那么他每一个子节点所在的子树之间的选择是互不干扰的,就是说不选x时,x的第一个儿子所在的子树选出了一个合法方案,x的第二个儿子所在的子树选出了一个合法方案,那么者两个儿子所在的子树选的点合起来一定还是合法的。那么我们设dp[i]为以i为根的子树合法方案的数量,对于同一个根的不同儿子所在的子树之间,我们可以用乘法原理来累计方案,最后再加上子树全部不选只选i自己的方案,就是子树的方案数。dp方程是 d p [ x ] = 1 + f a [ y ] == x d p [ y ] 。最后答案是 d p [ 0 ] 1 ,因为不存在只选择虚根的情况, d p [ 0 ] 中已经包含了选择空集的情况了。

代码:

#include <bits/stdc++.h>
using namespace std;

int n,cnt,hed[100010],num;
char s[60][60];
long long dp[100010];
struct node
{
    int vis[26],cnt;
}a[100010];
struct edge
{
    int next,to;
}b[100010];
void add(int from,int to)
{
    b[++num].to=to;
    b[num].next=hed[from];
    hed[from]=num;
}
void build(int x)
{
    int ji=0,m=strlen(s[x]+1);
    for(int i=1;i<=m;++i)
    {
        int y=s[x][i]-'a';
        if(!a[ji].vis[y])
        a[ji].vis[y]=++cnt;
        ji=a[ji].vis[y];
    }
    a[ji].cnt=1;
}
void rebuild(int x,int fa)
{
    if(a[x].cnt)
    {
        add(fa,++cnt);
        fa=cnt;
    }
    for(int i=0;i<=25;++i)
    {
        if(a[x].vis[i])
        rebuild(a[x].vis[i],fa);
    }
}
void dfs(int x)
{
    dp[x]=1;
    for(int i=hed[x];i;i=b[i].next)
    {
        int y=b[i].to;
        dfs(y);
        dp[x]*=dp[y];
    }
    dp[x]++;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        scanf("%s",s[i]+1);
        build(i);
    }
    cnt=0;
    rebuild(0,0);
    dfs(0);
    printf("%lld\n",dp[0]-1);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/forever_shi/article/details/82713210