​The Preliminary Contest for ICPC Asia Shanghai 2019 F. Rhyme scheme​

题目

题意很绕,其实大致就是给你 n 个数,让你划分集合,例如 n = 3;

集合划分为 :(一共有 5 种)

1 1 1

1 1 2

1 2 1

2 1 1

1 2 3

划分完集合后,让你用大写字母代表每个集合,但是从左往右填入字母按字典序来填:

The Preliminary Contest for ICPC Asia Shanghai 2019 F. Rhyme scheme (DP + int128)_#define


其实给定 n 后,划分集合的方式数就是贝尔数。现在问你 第 k 个字符串是多少。

分析

其实对于每一个字符串 str,str[i] 最大是前面出现过的最大字母 + 1。可以用字典树表示:

The Preliminary Contest for ICPC Asia Shanghai 2019 F. Rhyme scheme (DP + int128)_i++_02


现在问题变成问你这棵树第 n 层,第 m 个节点的路径。

找路径可以 DFS,但是暴力找不行。

如果我知道当前节点在第 n 层能扩展多少节点,那么对于每一层都可以直接确定走第几个节点。

可以用 dp 预处理下上面的。

代码

#include <bits/stdc++.h>
#define INF 0x3fffffff
#define fuck(x) cout << (x) << endl
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 1e6 + 10;

int t, n;
__int128 m, dp[100][100][100];
// dp[n][i][j] 表示长度为 n 时,在第 i 层,前面出现的字母最大是 j 有多少个

template <typename T> void read(T &x) { // __int128 要自己实现 IO
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}

void init(){ // 最多 26 层
for(int n = 1; n <= 26; n++){
for (int i = n; i >= 1; i--){
if(i == n){
for (int j = 1; j <= i; j++)
dp[n][i][j] = 1;
}else{
for(int j = 1; j <= i; j++){
dp[n][i][j] = dp[n][i + 1][j] * j + dp[n][i + 1][j + 1];
}
}
}
}
}

void dfs(int id, int mmax){ // 遍历字符串树, id 表示第几层, mmax表示遇到最大的字符
if(id == n)
return;
for (int i = 1; i <= mmax + 1; i++){ // 每层的上界都是遇到最大的字符 + 1
int tmp = max(mmax, i);
if(dp[n][id+1][tmp] < m){
m -= dp[n][id + 1][tmp];
}else{
putchar('A' + i - 1); // 选择输出第几个儿子
dfs(id + 1, tmp);
return;
}
}
}

int main(){
init();
scanf("%d", &t);
for(int cas = 1; cas <= t; cas++){
scanf("%d", &n);
read(m);
printf("Case #%d: ", cas);
putchar('A'); // 'A' 是根节点
dfs(1, 1);
puts("");
}
return 0;
}
/*
1 1 1 1 1
1 2 3 4 5
2 5 10 17 26
5 15 37 77 141
15 52 151 372 799
*/

另一种 dp:

#include <bits/stdc++.h>
#define INF 0x3fffffff
#define fuck(x) cout << (x) << endl
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 1e6 + 10;

int t, n;
__int128 m, dp[32][32];
// dp[i][j] 表示后面还有 i 位,前面出现最大字母是 j 的节点儿子代表的不同字典序的数量

template <typename T> void read(T &x) { // __int128 要自己实现 IO
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}

void init(){ // 最多 26 层
for (int i = 0; i <= 30; i++) // 初始化,第 0 层节点只有一个
dp[0][i] = 1;
for (int i = 1; i <= 30; i++)
for (int j = 0; j <= 30; j++)
dp[i][j] = dp[i - 1][j] * j + dp[i - 1][j + 1];
}

int main(){
init();
scanf("%d", &t);
for(int cas = 1; cas <= t; cas++){
scanf("%d", &n);
read(m);
printf("Case #%d: ", cas);
int tmp = 0, c = 0;
for(int i = 1; i <= n; i++){
for(c = 0; (m > dp[n-i][tmp]) && (c < tmp); c++)
m -= dp[n - i][tmp];
putchar('A' + c);
tmp = max(tmp, c + 1);
}
puts("");
}
return 0;
}
/*
1 1 1 1 1
1 2 3 4 5
2 5 10 17 26
5 15 37 77 141
15 52 151 372 799
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1000005;
int read(){
int f=1,g=0;
char ch=getchar();
for (;!isdigit(ch);ch=getchar()) if (ch=='-') ch=-1;
for (;isdigit(ch);ch=getchar()) g=g*10+ch-'0';
return f*g;
}
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int T,n;
__int128 m,f[32][32];
int main(){
for (int i=0;i<=30;i++) f[0][i]=1;
for (int i=1;i<=30;i++)
for (int j=0;j<=30;j++)
f[i][j]=f[i-1][j]*j+f[i-1][j+1];
T=read();
for (int op=1;op<=T;op++){
n=read();read(m);
printf("Case #%d: ",op);
int t=0,c=0;
for (int i=1;i<=n;i++){
for (c=0;(m>f[n-i][t])&&(c<t);c++) m-=f[n-i][t];
putchar('A'+c);
t=max(t,c+1);
}
puts("");
}
return 0;
}