让我们一起来%forever_shi神犇
题意: 给你n个字符串,每次选出若干个字符串形成一个集合,问有多少个集合满足集合中的任何一个字符串都不是另外一个字符串的前缀。空集也一定是满足条件的。保证不会出现两个相同的字符串。
首先对所有字符串建出一棵 t r i e trie trie树,然后我们可以发现其实 t r i e trie trie树上有用的点只有那些作为某个串的末尾被打上标记的点,于是我们根据 t r i e trie trie树上祖先后代的关系对这些有用的点建出一棵新树来(仍然保留虚根0号点)。
此时我们不难想到,如果新树上某个点被选了,那么合法的集合中它的子树内的所有其他节点都不能被选了;如果某个节点不被选,那么他每一个子节点所在的子树之间的选择是互不干扰的,就是说不选 x x x时, x x x的第一个儿子所在的子树选出了一个合法方案, x x x的第二个儿子所在的子树选出了一个合法方案,那么两个儿子所在的子树选的点合起来一定还是合法的。那么我们设 d p [ i ] dp[i] dp[i]为以 i i i为根的子树合法方案的数量,对于同一个根的不同儿子所在的子树之间,我们可以用乘法原理来累计方案,最后再加上子树全部不选只选i自己的方案,就是子树的方案数。 d p dp dp方程是 d p [ x ] = 1 + ∏ f a [ y ] = = x d p [ y ] dp[x]=1+\prod_{fa[y]==x}{dp[y]} dp[x]=1+∏fa[y]==xdp[y]。最后答案是 d p [ 0 ] − 1 dp[0]-1 dp[0]−1,因为不存在只选择虚根的情况, d p [ 0 ] dp[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;
}