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

本文详细解析了ICPC Asia Shanghai 2019预赛F题“韵律方案”,介绍了使用贝尔数和字典树解决组合问题的方法,通过动态规划优化搜索过程,提供两种代码实现思路。

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
划分完集合后,让你用大写字母代表每个集合,但是从左往右填入字母按字典序来填:
在这里插入图片描述
其实给定 n 后,划分集合的方式数就是贝尔数。现在问你 第 k 个字符串是多少。

分析

其实对于每一个字符串 str,str[i] 最大是前面出现过的最大字母 + 1。可以用字典树表示:
在这里插入图片描述
现在问题变成问你这棵树第 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值