洛谷 1666 字首單詞 trie樹 dp
阿新 • • 發佈:2018-12-10
題目連結 題意: 給你n個字串,每次選出若干個字串形成一個集合,問有多少個集合滿足集合中的任何一個字串都不是另外一個字串的字首。空集也一定是滿足條件的。保證不會出現兩個相同的字串。
題解:
也是NOIP模擬賽出現的題。當時想到了建trie樹之後dp(我也忘了是怎麼想到的了),但是當時我感覺算互相不為字首可能不太好算,於是就在考慮用集合總數減去存在互為字首的集合,結果發現自己dp計數出現了錯誤,只好交了暴力。聽完講評後研究了一下發現直接做其實也並不難做(又一次暴露出我菜)。
於是我來說一下講評時給出的做法。首先對所有字串建出一棵trie樹,然後我們可以發現其實trie樹上有用的點只有那些作為某個串的末尾被打上標記的點,於是我們根據trie樹上祖先後代的關係對這些有用的點建出一棵新樹來(仍然保留虛根0號點)。此時我們不難想到,如果新樹上某個點被選了,那麼合法的集合中它的子樹內的所有其他節點都不能被選了;如果某個節點不被選,那麼他每一個子節點所在的子樹之間的選擇是互不干擾的,就是說不選x時,x的第一個兒子所在的子樹選出了一個合法方案,x的第二個兒子所在的子樹選出了一個合法方案,那麼者兩個兒子所在的子樹選的點合起來一定還是合法的。那麼我們設dp[i]為以i為根的子樹合法方案的數量,對於同一個根的不同兒子所在的子樹之間,我們可以用乘法原理來累計方案,最後再加上子樹全部不選只選i自己的方案,就是子樹的方案數。dp方程是 。最後答案是,因為不存在只選擇虛根的情況,中已經包含了選擇空集的情況了。
程式碼:
#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;
}