多多鸡打算造一本自己的电子字典,里面的所有单词都只由a和b组成。
每个单词的组成里a的数量不能超过N个且b的数量不能超过M个。
多多鸡的幸运数字是K,它打算把所有满足条件的单词里的字典序第K小的单词找出来,作为字典的封面。
共一行,三个整数N, M, K。(0 < N, M < 50, 0 < K < 1,000,000,000,000,000)
共一行,为字典序第K小的单词。
2 1 4
ab
满足条件的单词里,按照字典序从小到大排列的结果是
a
aa
aab
ab
aba
b
ba
baa
对于40%的数据:0 < K < 100,000
对于100%的数据:0 < K < 1,000,000,000,000,000
题目保证第K小的单词一定存在
import java.util.*;
import java.math.*;
public class Main{
public static void main(String[] args){
BigInteger[][] dp = new BigInteger[50][50];
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int M = sc.nextInt();
long K = sc.nextLong();
for(int i=0;i<=N;i++){
dp[i][0] = new BigInteger(Integer.toString(i));
}
for(int i=0;i<=M;i++){
dp[0][i] = new BigInteger(Integer.toString(i));
}
for(int i=1;i<=N;i++){
for(int j=1;j<=M;j++){
//dp[i][j] = 1+dp[i-1][j] + 1+ dp[i][j-1];
dp[i][j] = dp[i-1][j].add(dp[i][j-1]).add(new BigInteger("2"));
}
}
StringBuilder sb = new StringBuilder();
int n = N, m = M;
long k = K;
while(k>0){
if(n>0 && m>0){
if(dp[n-1][m].compareTo(new BigInteger(Long.toString(k-1)))>=0){//k<=dp[n-1][m]+1
k--;
sb.append('a');
n--;
}else{ //k>dp[n-1][m]+1
k -= dp[n-1][m].longValue()+2;
sb.append('b');
m--;
}
}else if(n>0 && m==0){
k--;
sb.append('a');
n--;
}else if(n==0 && m>0){
k--;
sb.append('b');
m--;
}else{
k=0;
}
}
System.out.println(sb.toString());
}
} #include<iostream>
#include<vector>
#include<string>
using namespace std;
class Solution{
public:
vector<string> res;
vector<string> Magic_box(int n,int m,string tmp)
{
if(n!=0)
{
tmp.push_back('a');
res.push_back(tmp);
Magic_box(n-1,m,tmp);
tmp.pop_back();
}
if(m!=0)
{
tmp.push_back('b');
res.push_back(tmp);
Magic_box(n,m-1,tmp);
tmp.pop_back();
}
return res;
}
};
int main()
{
Solution s;
string tmp="";
int n,m,k;
cin>>n;
cin>>m;
cin>>k;
auto str=s.Magic_box(n,m,tmp);
for(int i=0;i<str[k-1].size();i++)
{
cout<<str[k-1][i];
}
return 0;
} 添加个golang的解析,思路与其他人差不多
package main
import "fmt"
func main() {
n, m, k := 0, 0, 0
fmt.Scanf("%d %d %d", &n, &m, &k)
dp := make([][]uint64, n+1)
//构造动态规划二维数组dp[n][m]
//dp[i][j]代表i个a,j个b所能拼接出的所有可能字符串的数量
for i := 0; i <= n; i++ {
dp[i] = make([]uint64, m+1)
dp[i][0] = uint64(i)
}
for j := 0; j <= m; j++ {
dp[0][j] = uint64(j)
}
for i := 1; i <= n; i++ {
for j := 1; j <= m; j++ {
dp[i][j] = 1 + dp[i-1][j] + 1 + dp[i][j-1]
}
}
res := ""
tmp := uint64(k)
ac, bc := n, m
for tmp != 0 {
//如果b的数量没了,则剩下的要填补a
if bc == 0 {
var i uint64
for i = 0; i < tmp; i++ {
res += "a"
}
break
}
//如果a的数量没了,则剩下的要填补b
if ac == 0 {
var i uint64
for i = 0; i < tmp; i++ {
res += "b"
}
break
}
// 如果当前的字符是a 则tmp的上限是 1+dp[ac-1][bc](dp[ac-1][bc]为ac-1个a,bc个b 所能拼接出的所有可能字符串的数量);
// 否则 当前字符应该是b
if tmp <= 1+dp[ac-1][bc] {
res += "a"
tmp -= 1
ac--
} else {
res += "b"
tmp -= 1 + dp[ac-1][bc] + 1
bc--
}
}
fmt.Printf("%s",res)
}
由不同的前缀a和b构成了两个子树,找第k个元素实际上就是先序遍历的过程,通过求子树中的唯一字符串个数可以快速地跳过一些无关的子树。
直接先序遍历往往会有过大的时间复杂度,可以通过带备忘录的递归算法求解出f(n,m)的值,从而避免大量的重复运算。
Base case:只有m或n不为零,则直接返回对应的值。

# 计算包含N个a和M个b的子字典树包含的unique字符串的个数,从而和K进行
# 比较,以便选择跳过该子树或者进入该子树
count_dict = {}
def calc_subtree_str_num(N,M):
if (N,M) in count_dict.keys():
return count_dict[(N,M)]
elif N == 0:
count_dict[(N,M)] = M
elif M == 0:
count_dict[(N,M)] = N
else:
#分别以a和b得到的两个子树的个数统计加上a和b这两个节点
count_dict[(N,M)] = calc_subtree_str_num(N-1,M) + calc_subtree_str_num(N,M-1) + 2
return count_dict[(N,M)]
def main():
N,M,K = list(map(int,input().split()))
res = ""
#当K=0时就找到了对应的字符串
while K > 0:
# 一般情况 N 和 M 都存在
if N > 0 and M > 0:
#获取当前情况下左子树的字符串个数,如果大于K说明结果在右子树
left_str_cnt = calc_subtree_str_num(N-1, M)+1
if K <= left_str_cnt:
# 说明第k个在a子树下,添加字符'a',并继续循环向下判断
res += 'a'
K -= 1
N -= 1
else:
# 在右子树下,K可以跳过left_str_cnt+1个值,1为左子树根节点'a',配合上前缀也是一个unique的str
res += 'b'
K -= (left_str_cnt + 1)
M -= 1
#其他情况,N=0或者M=0时只需要一直添加字符即可,因为题上的说明不用考虑不存在
elif N == 0 and M > 0:
res += 'b'
K -= 1
M -= 1
elif M == 0 and N > 0:
res += 'a'
K -= 1
N -= 1
return res
print(main())
#include <iostream>
#include <string>
#include <map>
using namespace std;
map<pair<int,int>,unsigned long long> ma;
unsigned long long f(int m,int n)
{
if(ma.count({m,n}))return ma[{m,n}];
if(!m)ma[{m,n}]=n;
else if(!n)ma[{m,n}]=m;
else ma[{m,n}]=f(m-1,n)+f(m,n-1)+2;
return ma[{m,n}];
}
int main()
{
long long k;
int n,m;
cin>>n>>m>>k;
string cur="a";
n--;
k--;
while(k>0&&(m||n))
{
unsigned long long step=f(n,m)+1;//子树的个数
if(step>k)//k在子树中
{
k--;
if(n)
{
cur+="a";
n--;
}else{
cur+="b";
m--;
}
}else{//k不在子树中,在下一个子树里
k-=step;
n++;
m--;
cur.back()++;
}
}
cout<<cur<<endl;
return 0;
} 参考leetcode 440写的,对比那个题目特殊的地方在于用map记录a个数小于n且b个数小于m的所有排列个数,从而防止重复计算。基本的思路在于构建一个字典树,然后对这个字典树进行先序遍历,必要时进行剪枝,
回溯不可以的话,相当N特别大,所以考虑用字典序在O(N)复杂度下解决该问题。 字典序又叫trie树,该算法思想在实际生活中大量应用。 默认大家有trie树的基础知识,下面这种方式就是根据构建的字典序进行适当的剪枝,因为我们并不需要遍历每一颗树。 只需要通过数学公式推导出我们想要的结果在哪个子树即可。 本题相当于有两个树,以a开头和以b开头的两颗二叉树。 每一次的cal_step函数是返回以当前节点"a"或者"b"开头的前序遍历需要经过的步数,这个不是由动态规划得到一个dp二维数组,然后直接返回即可。 这里说一下动态规划的递推公式: dp[i][j] = dp[i-1][j] + 1 + dp[i][j-1] + 1 什么意思呢? 我看评论里也有人说了,就是返回以"a"开头的和以"b"开头的个数。 可能有人还是不理解,这个时候拿出笔来写一写就明白了。 比如a[2][1] = dp[1][1] + dp[2][0] + 2 dp[1][1] + 1是以"a"开头的个数,即我们可以固定一个"a"在开头 这样就少了一个a可以用,那么只剩下了[a,b]可以组合 即:a,ab,ba,b; 然后再和固定的a开头的字母"a"组合,即aa,aab,aba,ab,这个时候组合就相当于是dp[1][1] 以a开头的组合显然少了一个"a"这个特殊的组合,显然dp[1][1] + 1才是以"a"开头的组合个数。 题解如下:
line = list(map(int,sys.stdin.readline().strip().split())) n = line[0] m = line[1] k = line[2] class Solution: map = dict() def findKthNumber(self, n: int, m: int, k: int) -> int: # dp[i][j] 表示i个a,j个b所能构成的字母组合 dp = [[0] * (m + 1) for _ in range(n + 1)] for i in range(n + 1): dp[i][0] = i for i in range(m + 1): dp[0][i] = i for i in range(1, n + 1): for j in range(1, m + 1): # i个a和j个b组成的个数为以a开头的和以b开头的加上特殊的a和b dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + 2 def cal_step(n, m): return dp[n][m] + 1 cur = "a" # 第一次,应该是以'a'开头的 n -= 1 k -= 1 while k > 0 and (n or m): step = cal_step(n, m) if step <= k: # k在下一个子树中 k -= step # 此时的n,m是为了计算dp[m][n] # 一轮过后,需要计算以b开头的,所以m-=1,n+=1 n += 1 m -= 1 cur = cur[:-1] + "b" else: # 在子树中 k -= 1 if n: cur += "a" n -= 1 else: cur += "b" m -= 1 return cur print(Solution().findKthNumber(n, m, k))
def getcount(map,m,n):
if not m:
map[(m,n)]=n
if not n:
map[(m,n)]=m
elif (m,n) in map:
return map[(m,n)]
else:
#+'a'就是getcount(map,m-1,n)+1,加'b'同理
map[(m,n)]=getcount(map,m-1,n)+getcount(map,m,n-1)+2
return map[(m,n)]
def mink(n,m,k):
cur=""
map= {}
while k>0 :
if n>0 and m==0:#只有'a'存在
k-=1
cur+="a"
n-=1
elif m>0 and n==0: #只有'b'存在
cur+='b'
k-=1
m-=1
elif n>0 and m>0 :#都存在时
#计算a下所有子节点的和
cnt=getcount(map,n-1,m) +1
if cnt>=k:#在a子树下
cur+='a'
k-=1 #k相当于横向遍历,每次向右遍历一个节点
n-=1
else:#在b子树下
cur+='b'
m-=1
k-=(cnt+1) #k要减去a子树的所有节点个数
return cur
from collections import defaultdict
ss=list(map(int,input().split()))
n=ss[0]
m=ss[1]
k=ss[2]
p=mink(n,m,k)
print(p) import java.util.Scanner;
import java.util.Arrays;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
static int n, m;
static long k;
static StringBuffer path = new StringBuffer();
static long [][] dist = new long [55][55];
static void dfs(int da, int db) {
if (dist[n - da][m - db] != -1) {
if (k - dist[n-da][m-db] > 0) {
k -= dist[n-da][m-db];
return;
}
}
long cnt = k;
k --;
if (k == 0) {
System.out.println(path);
return;
}
if (k < 0) return;
if (da < n) {
path.append("a");
dfs(da + 1, db);
path.deleteCharAt(da + db);
}
if (db < m) {
path.append("b");
dfs(da, db + 1);
path.deleteCharAt(da + db);
}
cnt -= k; // 统计子节点个数(包括自身)
dist[n-da][m-db] = cnt;
}
static void problem2020_5() {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
m = scanner.nextInt();
k = scanner.nextLong() + 1;
for (int i = 0; i < dist[0].length; i ++ ) {
Arrays.fill(dist[i], -1L);
}
dfs(0, 0);
}
public static void main(String[] args) {
problem2020_5();
}
}
dfs,排除等效冗余优化可以ac
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll MX=10000000000000000LL;
ll dp[55][55];
ll dfs(int n,int m){
if(n==0||m==0)return m+n;
if(~dp[n][m])return dp[n][m];
return dp[n][m]= min(dfs(n-1,m)+ dfs(n,m-1)+2,MX);
}
int main()
{
ll n,m,k;
cin>>n>>m>>k;
memset(dp,-1,sizeof dp);
string s="";
while(1){
if(n>0&&dfs(n-1,m)+1>=k){
s+="a";
n--;
}else{
if(n>0)k-= dfs(n-1,m)+1;
s+="b";
m--;
}
k--;
if(k==0)break;
}
cout<<s<<endl;
}
global rec
rec = {}
def count(N, M):
global rec
if not N:
return M
if not M:
return N
if (N, M) in rec:
return rec[(N, M)]
rec[(N, M)] = count(N-1, M) + 1 + count(N, M-1) + 1
return rec[(N, M)]
def helper(N, M, K):
global rec, ans
count(N, M)
ans = ''
def dfs(N, M, K, res):
global ans
if K<=0:
ans = res
return
elif not N:
ans = res + 'b' * K
return
elif not M:
ans = res + 'a' * K
return
#print(N, M, K, count(N-1, M))
if count(N-1, M)+1 >= K:
dfs(N-1, M, K-1, res+'a')
else:
dfs(N, M-1, K-count(N-1, M)-2, res+'b')
dfs(N, M, K, '')
print(ans) import java.math.BigDecimal;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//N个a
int N = sc.nextInt();
//M个b
int M = sc.nextInt();
//第K个单词
long K = sc.nextLong();
System.out.println(dic(N,M,K));
}
//N个a,M个b
public static String dic(int N, int M, long K){
BigDecimal index = BigDecimal.valueOf(0);
StringBuilder s = new StringBuilder();
int num_a = N;
int num_b = M;
while (BigDecimal.valueOf(K).compareTo(index) > 0 && num_a!=0 && num_b!=0){
if (BigDecimal.valueOf(K).compareTo(index.add(cal(num_a - 1, num_b)).add(BigDecimal.valueOf(2))) > 0){
s.append("b");
index = index.add(cal(num_a - 1, num_b)).add(BigDecimal.valueOf(2));
num_b--;
}
else if (BigDecimal.valueOf(K).compareTo(index.add(cal(num_a - 1, num_b)).add(BigDecimal.valueOf(2))) == 0){
s.append("b");
return s.toString();
}
else {
s.append("a");
index = index.add(BigDecimal.valueOf(1));
num_a--;
}
}
if (num_a==0){
for (long i =0;i<K-index.longValue(); i++){
s.append("b");
}
}
if (num_b==0){
for (long i =0;i<K-index.longValue(); i++){
s.append("a");
}
}
return s.toString();
}
//解决 x个a和y个b有多少种排列方式
public static BigDecimal cal(int x, int y){
BigDecimal res = BigDecimal.valueOf(0);
int min = Math.min(x,y);
int max = Math.max(x,y);
for (int i=1; i<=min; i++){
res = res.add(new BigDecimal(2).pow(i));
}
for (int i = min+1; i<=x+y; i++){
BigDecimal e = BigDecimal.valueOf(0);
for (int j = min ;j>=i-max;j--){
e = e.add(doFactorial(i).divide((doFactorial(j).multiply(doFactorial(i - j))), 2));
}
res = res.add(e);
}
return res;
}
public static BigDecimal doFactorial(int n){
if(n<0){
return BigDecimal.valueOf(-1);//传入的数据不合法
}
if(n==0){
return BigDecimal.valueOf(1);
}else if(n==1){//递归结束的条件
return BigDecimal.valueOf(1);
}else{
return BigDecimal.valueOf(n).multiply(doFactorial(n-1));
}
}
}