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;
}