2019 上海网络赛 F. Rhyme scheme(dp + 线性构造)

6 篇文章 0 订阅

在这里插入图片描述
题目大意:用字符表示集合划分,例如 AABBA 表示 1,2,5划分一个集合,3,4划分一个集合,输入 n,k,用字母表示 n 个人的集合划分,输出字典序第 k 小的划分字符串。


集合划分的数目是一个贝尔数,观察到如果前 i 个人划分出了 k 个集合,第 i + 1 个人有 k + 1种选择,要么归属 k 个集合中的一个,要么划分到一个新的集合。一个人的可选择方案数量只和前面的集合数量有关,并且第一个人总是划分到A,因为 AB 和 BA是一样的解,那么第一个人总可以划分到A。那么可以画出一个每个人的划分选择解答树:
在这里插入图片描述
可以沿着树dfs,用 dp 记录每个结点往下的方案数,更一般的,一个结点往下的方案数和当前这个点划分到哪个集合无关,而与这个点以及这个点之前所划分的集合数目有关。

接下来考虑沿着树链构造解,构造解的过程类似于树上二分的过程,当处于第一层的A结点时,考虑先往左儿子A结点走,若往左走方案数不够多,则往右儿子结点B走,且 询问数 - 左儿子方案数。

对于多叉树,依次从左往右遍历儿子即可,找到第一个可以往下走的儿子往下走。
(然后由于贝尔数第26项爆了 ll ,要用__int128,坑得一批)


代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e3 + 10;
typedef __int128 int128;
__int128 dp[30][30][30];
inline __int128 read() {
 	__int128 x=0,f=1;
 	char ch=getchar();
 	while(ch<'0'||ch>'9') {
 		if(ch=='-')
 			f=-1;
 		ch=getchar();
 	}
 	while(ch>='0'&&ch<='9') {
 		x=x*10+ch-'0';
 		ch=getchar();
 	}
 	return x*f;
}
void print(__int128 x)
{
	if (!x) return ;
	if (x < 0) putchar('-'),x = -x;
	print(x / 10);
	putchar(x % 10 + '0');
}
__int128 dfs(int mx,int len,int cur) {
	if(cur == len) return dp[len][mx][cur] = 1;
	if(dp[len][mx][cur] != -1) return dp[len][mx][cur];
	__int128 & ans = dp[len][mx][cur] = 0;
	for(int i = 1; i <= min(mx + 1,26); i++) {
		if(i >= mx)
			ans += dfs(i,len,cur + 1);
		else 
			ans += dfs(mx,len,cur + 1);
	}
	return ans;
}
int t,n;
__int128 k;
int main() {
	memset(dp,-1,sizeof dp);
	for(int i = 1; i <= 26; i++)
		ll ans = dfs(1,i,1);
	scanf("%d",&t);
	int ca = 0;
	while(t--) {
		scanf("%d",&n);
		k = read();
		int lst = 1;
		printf("Case #%d: ",++ca);
		for(int i = 1; i <= n; i++) {
			for(int j = 1; j <= lst + 1; j++) {
				int p = max(lst,j);
				if(dp[n][p][i] < k)
					k -= dp[n][p][i];
				else {
					putchar('A' + j - 1);
					lst = max(lst,p);
					break;
				}
			}
		}
		puts("");
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值