1. 程式人生 > >trie樹 dp 字首單詞

trie樹 dp 字首單詞

讓我們一起來%forever_shi神犇

題意: 給你n個字串,每次選出若干個字串形成一個集合,問有多少個集合滿足集合中的任何一個字串都不是另外一個字串的字首。空集也一定是滿足條件的。保證不會出現兩個相同的字串。

首先對所有字串建出一棵 t r i e trie

樹,然後我們可以發現其實 t r i e trie 樹上有用的點只有那些作為某個串的末尾被打上標記的點,於是我們根據 t
r i e trie
樹上祖先後代的關係對這些有用的點建出一棵新樹來(仍然保留虛根0號點)。

此時我們不難想到,如果新樹上某個點被選了,那麼合法的集合中它的子樹內的所有其他節點都不能被選了;如果某個節點不被選,那麼他每一個子節點所在的子樹之間的選擇是互不干擾的,就是說不選 x

x 時, x x 的第一個兒子所在的子樹選出了一個合法方案, x x 的第二個兒子所在的子樹選出了一個合法方案,那麼兩個兒子所在的子樹選的點合起來一定還是合法的。那麼我們設 d p [ i ] dp[i] 為以 i i 為根的子樹合法方案的數量,對於同一個根的不同兒子所在的子樹之間,我們可以用乘法原理來累計方案,最後再加上子樹全部不選只選i自己的方案,就是子樹的方案數。 d p dp 方程是 d p [ x ] = 1 + f a [ y ] = = x d p [ y ] dp[x]=1+\prod_{fa[y]==x}{dp[y]} 。最後答案是 d p [ 0 ] 1 dp[0]-1 ,因為不存在只選擇虛根的情況, d p [ 0 ] dp[0] 中已經包含了選擇空集的情況了。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
struct node
{
    int next,to;
}e[100100];
struct nodee
{
    int cnt,vis[30];
}a[100100];
int n,head[100100],num,cnt;
ll dp[10000];
char s[100][100];
void add(int from,int to)
{
    e[++num].next=head[from];
    e[num].to=to;	
    head[from]=num;
}
void add1(int x)//建trie樹 
{
    int tmp=0;
    int len=strlen(s[x]+1);
    for(int i=1;i<=len;++i)
    {
        int v=s[x][i]-'a'+1;
        if(!a[tmp].vis[v])
        	a[tmp].vis[v]=++cnt;
        tmp=a[tmp].vis[v];
    }
    a[tmp].cnt=1;//記錄是否是最後一個字母 
}
void build(int x,int fa)//建出以末尾為節點的樹 
{
    if(a[x].cnt)
    {
        add(fa,++cnt);
        fa=cnt;
    }
    for(int i=1;i<=26;++i)
        if(a[x].vis[i])
        	build(a[x].vis[i],fa);
}
void dfs(int x)
{
    dp[x]=1;
    for(int i=head[x];i;i=e[i].next)
    {
        int v=e[i].to;
        dfs(v);
        dp[x]*=dp[v];
    }
    dp[x]++;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;++i)
    {
        scanf("%s",s[i]+1);
        add1(i);
    }	
    cnt=0;
    build(0,0);
    dfs(0);
    cout<<dp[0]-1;
    return 0;
}