欢迎来到蒟蒻mqd的博客

2019上海网络赛 F. Rhyme scheme 普通dp

Rhyme scheme

Problem Describe

A rhyme scheme is the pattern of rhymes at the end of each line of a poem or song. It is usually referred to by using letters to indicate which lines rhyme; lines designated with the same letter all rhyme with each other.

e.g., the following "poem'' of 4 lines has an associated rhyme scheme "ABBA''

1 —— 99 bugs in the code A

2 —— Fix one line B

3 —— Should be fine B

4 —— 100 bugs in the code A

This essentially means that line 1 and 4 rhyme together and line 2 and 3 rhyme together.

The number of different possible rhyme schemes for an nnn-line poem is given by the Bell numbers. For example, \(B_3 = 5\), it means there are five rhyme schemes for a three-line poem: AAA, AAB, ABA, ABB, and ABC.

The question is to output the \(k\)-th rhyme scheme in alphabetical order for a poem of nnn lines.For example: the first rhyme scheme of a three-line poem is "AAA'', the fourth rhyme scheme of a three-line poem is ABB''.

InputFile

The first line of the input gives the number of test cases, T(\(1 \leq T \leq 10000\)). T test cases follow.

Each test case contains a line with two integers nnn and \(k\).

\(1 \leq n \leq 26, 1 \leq k \leq B_n\) (\(B_n\) is the \(n\)-th of Bell numbers)

OutputFile

For each test case, output one line containing Case #x: y, where x is the test case number (starting from 1) and y is the rhyme scheme contains uppercase letters.

样例输入

7
1 1
2 1
3 1
3 2
3 3
3 4
3 5

样例输出

Case #1: A
Case #2: AA
Case #3: AAA
Case #4: AAB
Case #5: ABA
Case #6: ABB
Case #7: ABC

题意

就是求第k个排列,但是有些排列是重复的,比如AAB和AAC算一种(如果不能理解可以看看题目)

题解

这里其实都是瞎扯

我一开始想的就是求当\(n=26\)时总共有多少种不同的排列。然后想了个dp,然后想用递归,每次确定第一位选什么,然后再做后面的。但是我的两个队友听到我的想法都说不懂,然后看别的题了,然后我就一个人想啊想,一路上遇到了许多问题,还好最后还是解决了。

首先我们需要发现一些性质:

  1. 长度为n的字符串,只会用n种字母,这个性质很明显也很重要。
  2. 每个字符串的种类会因为前面的不同而变化,比如aaa后面接两种字母a、b,而aab后面可以接a、b、c.
  3. 我们只要前面字符串用过的种类数以及后面字符串的长度,后面字符串的种类也就确定了
  4. 有人可能会觉得要是前面字符串用a、b、c和用a、b、d都是三种但是不一样,但是a、b、d是不会出现的(可以结合第一点想想)

以上的性质可不是那么容易就能明白的,所以请结合具体例子仔细想想。

题解正式开始

先说说dp的预处理吧:

  • \(\Large 定义:\) \(dp[n][m] [h]\)用m种字母组成长度为m的字符串,字符串前面接一个用了h种字母的字符串(不在乎前面的字符串的长度)。
  • \(\Large 转移方程:\)\(dp[n][m][h]=dp[n][m-1][h]+dp[n][m][h]*m\)
  • \(\Large初始化:\) \(dp[0][0][h]=h\)

然后定义\(f[n][h]\)\(sum(dp[n][1..m][h])\),之后开始从第一位开始枚举,字符从小到大的开始枚举。

我这里是用的的递归,void find(n,k,h)​ 即前面的字符串用了h种字符,求接下n个字母组成的字符串的第k个排列。

然后一层层做下去,直到\(n=0\) 结束

ps:会爆int64,于是我用了int128;还有就是我并没有讲的太清楚,只是讲了主要思想和具体实现,至于怎么想到的,那我也不清楚。

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
template<typename T>void read(T&x)
{
    ll k=0; char c=getchar();
    x=0;
    while(!isdigit(c)&&c!=EOF)k^=c=='-',c=getchar();
    if (c==EOF)exit(0);
    while(isdigit(c))x=x*10+c-'0',c=getchar();
    x=k?-x:x;
}
void read_char(char &c)
{while(!isalpha(c=getchar())&&c!=EOF);}

__int128 f[28][28],dp[28][28][28],k;
ll n;
void init()//预处理部分
{
    for(ll i=0;i<=26;i++)dp[0][i][i]=1;
    for(ll h=0;h<=26;h++)
        for(ll i=1;i<=26;i++)
            for(ll j=1;j<=26;j++)
                dp[i][j][h]=dp[i-1][j][h]*j+dp[i-1][j-1][h];
    for(ll h=0;h<=26;h++)
        for(ll i=0;i<=26;i++)
            for(ll j=0;j<=26;j++)
                f[i][h]+=dp[i][j][h];
}
void Find(ll n,__int128 k,ll h)//递归求解
{
    if (n==0)return;
    ll tp=(k-1)/f[n-1][h];
    if (tp+1<=h)//格外注意h=0的时候
    {
        printf("%c",'A'+(int)(tp));
        Find(n-1,k-tp*f[n-1][h],h);
    }
    else
    {
        printf("%c",'A'+(int)(h));
        Find(n-1,k-h*f[n-1][h],h+1);
    }
}

void work()
{
    static int cas=0;
    printf("Case #%d: ",++cas);
    read(n); read(k);
    ll p=0;
    Find(n,k,p);
    if (cas)printf("\n");
}
int main()
{
#ifndef ONLINE_JUDGE
    freopen("aa.in","r",stdin);
#endif
    init();
    int T;
    read(T);
    while(T--)work();
}

posted @ 2019-09-15 19:06  mmqqdd  阅读(478)  评论(0编辑  收藏  举报