第k个字符串
题目描述
给定两个整数 n n n 和 k k k。
用 n − 2 n−2 n−2 个 a a a 和 2 2 2 个 b b b 来构成一个字符串,则一共可以构成 n ( n − 1 ) / 2 n(n−1)/2 n(n−1)/2 个不同的字符串。
将这 n ( n − 1 ) / 2 n(n−1)/2 n(n−1)/2 个字符串按照字典序进行排序。
请输出排好序后,排在第 k k k 个的字符串。
例如,当 n = 5 n=5 n=5, k = 2 k=2 k=2 时,共可以生成 10 10 10 个不同的字符串,按字典序排列如下:
aaabb
aabab
aabba
abaab
ababa
abbaa
baaab
baaba
babaa
bbaaa
其中,排在第 2 个的字符串为 aabab。
输入格式
第一行包含整数 T,表示共有 T 组测试数据。
每组数据占一行,包含两个整数 n 和 k。
输出格式
每组数据输出一行结果,表示答案。
样例
输入样例:
7
5 1
5 2
5 8
5 10
3 1
3 2
20 100
输出样例:
aaabb
aabab
baaba
bbaaa
abb
bab
aaaaabaaaaabaaaaaaaa
数据范围与提示
30% 测试点满足
1
≤
T
≤
10
,
3
≤
n
≤
20
,
1
≤
k
≤
100
1≤T≤10,3≤n≤20,1≤k≤100
1≤T≤10,3≤n≤20,1≤k≤100。
60% 测试点满足 3 ≤ n ≤ 100 3≤n≤100 3≤n≤100。
80% 测试点满足 3 ≤ n ≤ 10000 3≤n≤10000 3≤n≤10000。
100% 测试点满足 1 ≤ T ≤ 10000 , 3 ≤ n ≤ 1 0 5 , 1 ≤ k ≤ m i n ( 2 × 1 0 9 , n ( n − 1 ) / 2 ) 1≤T≤10000,3≤n≤10^5,1≤k≤min(2×10^9,n(n−1)/2) 1≤T≤10000,3≤n≤105,1≤k≤min(2×109,n(n−1)/2)。
同一测试点内所有 n n n 的和不超过 1 0 5 10^5 105。
解题分析
方法1:排列枚举
思路:从起始字符串开始,一个一个往后枚举下一个排列,达到第k个时输出。
优点:简单
缺点:一个一个枚举,太耗时,预计能过60%测试点。
int main()
{
int t;
cin >> t;
for (int x = 1; x <= t; x++) // 一共有t组
{
int n, k;
cin >> n >> k;
string s; // 根据n和k,产生初始字符串:aa...aabb
for (int i = 1; i <= n - 2; i++) // n-1个a
s = s + 'a';
s = s + "bb"; // 2个b
for (int i = 1; i <= k-1; i++) // 循环k-1次
{
next_permutation(s.begin(), s.end()); // 枚举下一个排列
}
cout << s << endl; // 输出第k个排列
}
return 0;
}
方法2:循环枚举
思路:循环枚举两个b的位置。
(1)通过观察我们发现,第一个b的位置 i 的变化范围是n到1;第二个b的位置 j 的变化范围是n-1到 i+1(到第1个b的位置 i 的后面)。
(2)当枚举到第k种情况时,输出结果。
优点:把枚举变为了2重循环,能通过
n
=
1
0
4
n=10^4
n=104的测试点。因为
n
=
1
0
4
n=10^4
n=104,两重循环是
1
0
8
10^8
108,再10组测试则是
1
0
9
10^9
109。
缺点:一个一个枚举,还是有些耗时,预计能过80%测试点。
int main()
{
int t;
cin >> t;
for (int x = 1; x <= t; x++)
{
int n, k;
cin >> n >> k;
int c = 0; // 计数器
for (int i = n - 1; i >= 1; i--) // 第1个b的位置i的变化范围
{
for (int j = n; j >= i + 1; j--) // 第1个b的位置j的变化范围
{
c++;
if (c == k) // 计数器到达第k个
{
for (int z = 1; z <= n; z++) // 根据两个b的位置i和j,输出字符串结果
{
if (z == i || z == j) // 当位置是i或j时,输出b
cout << 'b';
else
cout << 'a';
}
cout << endl;
j=0; // 退出i和j所在的2重循环
i=0;
}
}
}
}
return 0;
}
方法3:优化循环枚举
思路:优化第2个b的枚举。
(1)如果第2个b从n到i+1,还没达到k,则第1个b往前走1个,直接省掉第2个b的一轮循环。
(2)如果第2个b从n到i+1,可以达到或超过k,则可以直接计算第2个b的最终位置 j。
优点:通过优化,把第2个b的循环省掉了。能通过
n
=
1
0
5
n=10^5
n=105,
1
0
4
10^4
104组测试的数据。满分方法。
int main()
{
int t;
cin >> t;
for (int x = 1; x <= t; x++)
{
int n, k;
cin >> n >> k;
int c = 0; // 计数器
for (int i = n - 1; i >= 1; i--) // 第1个b的位置i的变化范围
{
if(c+n-i<k) // 本轮第2个b走到i+1,也不能达到k
{
c=c+(n-i);
}
else // 计数器可以达到k
{
int j=n-(k-c)+1; // 计算第2个b的位置j
for (int z = 1; z <= n; z++) // 根据两个b的位置i和j,输出字符串结果
{
if (z == i || z == j) // 当位置是i或j时,输出b
cout << 'b';
else
cout << 'a';
}
cout << endl;
j=0; // 退出i和j所在的2重循环
i=0;
}
}
}
return 0;
}