【XSY2344】K-th String(DP)

Description
Alice有 n(n≤26) 张牌,牌上分别标有前 n 个英文小写字母。例如,如果 n=3 ,则Alice有3张牌,分别标有"a", “b”, “c” 。Alice可以通过排列这些卡牌来构造字符串 t 。考虑字符串 t 的所有子串(共 n(n+1)2 个),按照字典序从小到大排名第 k 的子串为 s 。现在,给你正整数 n,k 和字符串 s ,问有多少种可能的字符串 t 。将答案对 109+7 取模。

例如: 当 n=3,t=“cab” 时,排序后的子串为"a", “ab”, “b”, “ca”, “cab”, “cab”,排名第3的子串为"b"。当 n=3,k=3,s=“b” 时 ,则 t 可能为"cab"或"bac" ,故答案为2种。

Input
第一行两个整数 n , k ( 1 ≤ n ≤ 26 , 1 ≤ k ≤ n ( n + 1 ) / 2 ) n,k(1≤n≤26,1≤k≤n(n+1)/2) n,k(1n26,1kn(n+1)/2)

第二行一个字符串 s ,s 中仅包含前 n 个字母,且 s 中的字母两两不同。

Output

输出一行表示答案。将答案对 109+7 取模。

Sample Input

3 3
b

Sample Output

2

HINT

数据范围与约定

对于30%的数据, 1 ≤ n ≤ 8 1≤n≤8 1n8

对于所有数据, 1 ≤ n ≤ 26 1≤n≤26 1n26

想象一下DP

我们先枚举s串

d p [ i ] [ j ] [ k ] [ l ] dp[i][j][k][l] dp[i][j][k][l]表示前 i i i个字母中,有 j j j个比 s [ 1 ] s[1] s[1]小,他们对答案的贡献为 k k k(添加这个节点后会有多少个新的小于 s [ 1 ] s[1] s[1]的串), l = 0 或 1 l=0或1 l=01,表示现在所取的子串中,有没有s这个串的方案数。

三种情况状态转移:

1.不取 i i i这个点: dp[i+1][j][kk][l]=dp[i+1][j][kk][l]+dp[i][j][kk][l]

2.取 i i i这个点:

一个点的贡献就是包含这个点在内,剩余子串的长度。

dp[i+1][j+1][kk+n-i][l]=dp[i+1][j+1][kk+n-i][l]+dp[i][j][kk][l]

3.直接取整个s串(前提:之前没取过s):

直接取s串的贡献就是对s串中的每一个点都求贡献

if(i+len<=n&&!l)
 {
          dp[i+len][j][kk+(n-i)*sum1-sum][1]=(dp[i+len][j][kk+(n-i)*sum1-sum][1]+dp[i][j][kk][l])%mod;
 }

最后统计答案:

因为在s串之前的字母的每一种排列都符合要求,所以答案要乘上排列的情况数。

s串之后的字母同理。

代码:

#include<bits/stdc++.h>
#define mod 1000000007
using namespace std;
int n,k,dp[27][27][3050][2],sum,sum1;
char ch[27];
int main()
{
    scanf("%d%d%s",&n,&k,ch+1);
    int len=strlen(ch+1);
    k-=len;
    if(k<0)
    {
        puts("0");
        return 0;
    }
    int num=ch[1]-'a'+1;
    for(int i=1;i<=len;i++)
    {
        if(ch[i]<=ch[1])
        {
            num--;
            if(ch[i]!=ch[1])
            {
                sum=sum+i-1;
                sum1++;//在s串之内的小于s[1]的字母的个数
            }
        }
    }
    dp[0][0][0][0]=1;
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
        {
            for(int kk=0;kk<=n*(n+1)/2;kk++)
            {
                for(int l=0;l<=1;l++)
                {
                    dp[i+1][j][kk][l]=(dp[i+1][j][kk][l]+dp[i][j][kk][l])%mod;//不取
                    dp[i+1][j+1][kk+n-i][l]=(dp[i+1][j+1][kk+n-i][l]+dp[i][j][kk][l])%mod;//取
                    if(i+len<=n&&!l)
                    {
                        dp[i+len][j][kk+(n-i)*sum1-sum][1]=(dp[i+len][j][kk+(n-i)*sum1-sum][1]+dp[i][j][kk][l])%mod;//整个s串
                    }
                }
            }
        }
    }
    long long ans=dp[n][num][k][1];//答案的一种
    for(int i=1;i<=num;i++)//乘上头和尾的排列数
    {
        ans=(ans*i)%mod;
    }
    for(int i=1;i<=n-num-len;i++)
    {
        ans=(ans*i)%mod;
    }
    printf("%lld\n",ans);
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值