2020寒假【gmoj1733】【ranking】【树状DP】

题目描述

小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;
}
发布了63 篇原创文章 · 获赞 61 · 访问量 5468

猜你喜欢

转载自blog.csdn.net/dglyr/article/details/104282653