Comet OJ 模拟赛测试Day2 天天背单词

https://www.cometoj.com/contest/70/problem/B?problem_id=3978

Description:


在n个单词中选k个组合成字符串,现给出一个组合成的字符串,求其按字典序的排名。且不存在某个单词是另一个单词的前缀

Solution


关键在于,不存在某个单词是另一个单词的前缀。

对于给出的字符串,必定有单词是它的前缀,并且这个前缀唯一。将这个前缀去掉以后,继续上述操作,可以说明,该字符串可以唯一分解为若干个单词的组合。

题目可转化为求字典序比这个字符串小的组合方案有多少个。首先找出首字母比该字符串小的单词做第一个单词,之后再选k-1个单词任意排列,所得组合必定满足条件。之后需要计算第一个字符与该字符串相同,但第二个不相同的比该字符串字典序小的合法方案。现在我们需要给构造的字符串选择第一个单词,使得这个单词首字母与该字符串首字母相同,但第二个字母小。类似如此操作下去。

需要思考的是,对于构造出的字符串怎样判断剩下还能再选几个单词。构造的字符串和原字符串有相同前缀,即有若干公共前缀单词,剩余的部分是某个单词的前缀。假设前面有x个单词与原字符串相同,再多出一部分,所以就要再选k-x-1个单词。

对于查找与某单词有某段公共前缀并字典序较小的单词个数,可用trie树完成

Code


#include <bits/stdc++.h>
using namespace std;
int n,k,cnt[1000010],t[1000010][28],num,len;
char S[1000010];
bool v[1000010];
long long Pow[1000010],ans,Mod=1e9+7;
void Insert(int p,int now)
{
	cnt[p]++;
	if (now>len) return;
	if (!t[p][int(S[now]-96)]) t[p][int(S[now]-96)]=++num;
	Insert(t[p][int(S[now]-96)],now+1);
}
int Find(int p,int now)
{
	if (now>len) return 0;
	if (!t[p][int(S[now]-96)])return 0;
	return Find(t[p][int(S[now]-96)],now+1)+1;
}
void Remove(int p,int l,int r)
{
	cnt[p]--;
	if (l>r) return;
	Remove(t[p][int(S[l]-96)],l+1,r);
}
long long ksm(long long aa,long long bb)
{
	long long an=1;
	for (;bb;bb>>=1) {
		if (bb&1) an=an*aa%Mod;
		aa=aa*aa%Mod;
    }
    return an;
} 
int main()
{
	scanf("%d%d",&n,&k);
	for (int i=1;i<=n;i++) {
		scanf("%s",S+1);
		len=strlen(S+1);
		Insert(0,1);
	}
	scanf("%s",S+1);
	len=strlen(S+1);
	Pow[0]=1;
	for (int i=1;i<=n;i++)
	 Pow[i]=Pow[i-1]*i%Mod;
	long long temp=ksm(Pow[n-k],Mod-2);
	v[1]=true;
	for (int i=1;i<=len;)	{
		int x=Find(0,i);
		v[i+x]=true;
		i+=x;
	}
	int x=0,s=0,las=0;
    for (int i=1;i<=len;i++) {
    	long long sum=0;
		if (v[i]) {
			x=0,s++;
			if (i>1) Remove(0,las,i-1);//选过的单词不能再用
			las=i;
	    }
		else x=t[x][int(S[i-1]-96)];
		for (int j=1;j<int(S[i]-96);j++)
		  if (t[x][j]) sum+=cnt[t[x][j]];
		ans=(ans+sum*Pow[n-s]%Mod*temp%Mod)%Mod;
    }
    printf("%lld",ans+1);
    return 0;
}
			 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值