题目描述
小x有n个小姊妹(根据典故,我们假设n≤3000)。他每天都喜欢按不同标准给小姊妹们排(打)序(分)。今天,他突然对小姊妹们的名字产生了兴趣。他觉得小姊妹的魅力和她们的名字有密切联系,于是他觉得所有有相似的名字的小姊妹必须排在一起。
相似是指,名字的开头一个或若干个连续字母相同。
于是,小x定下了如下规则:
在任何以同样的字母序列开头的名字之间,所有名字开头必须是同样的字母序列。
比如,像MARTHA和MARY这两个名字,它们都以MAR开头,所以像MARCO或MARVIN这样的名字可以插入这两个名字中间,而像MAY这样的就不行。
显然,按字典序排序是一个合法的排序方案,但它不是唯一的方案。你的任务就是计算出所有合法的方案数。考虑到答案可能很大,输出答案 mod 1 000 000 007。
输入
第一行一个整数n,小x的小姊妹个数。
第2~n+1行,每行一个字符串,代表这个小姊妹的名字。
输出
一行一个整数,合法的方案数。
样例输入
3
IVO
JASNA
JOSIPA
样例输出
4
分析
这题感觉像类似DFS的DP。。。
但经过fy大佬的指点以及不懈打分终于搞定:
我们可以把所有的字符串排序,这样相同的前缀的就全部相邻了。然后我们依照这个建一颗字典树。
然后在字典树上DP:设fi表示这个结点以下的点的排列方案数。fi=fx(x是i的儿子)*(i儿子个数的阶乘)。
f[0]就是答案。
上代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
const int mod=1000000007;
int n;
long long j[3011],ans;
string a[3011];
long long jc(int k)
{
if(k==0) return 0;
if(j[k]) return j[k];
j[k]=(jc(k-1)*k)%mod;
return j[k];
}
long long dp(int k,int x,int len)
{
if(len==1) return 1;
int vis[30],st[30],count=0;
memset(vis,0,sizeof(vis));
memset(st,0,sizeof(st));
long long r=1;
for(int i=x+len-1;i>=x;i--)
{
if(k>=a[i].length())
{
vis[0]++;
st[0]=i;
continue;
}
++vis[a[i][k]-'A'+1];
st[a[i][k]-'A'+1]=i;
}
for(int i=0;i<=26;i++)
{
if(vis[i])
{
r=(r*dp(k+1,st[i],vis[i]))%mod;
count++;
}
}
return (r*jc(count))%mod;
}
int main()
{
freopen("ranking.in","r",stdin);
freopen("ranking.out","w",stdout);
scanf("%d",&n);
j[1]=1;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
sort(a+1,a+n+1);
cout<<dp(0,1,n);
fclose(stdin);
fclose(stdout);
return 0;
}