途灵编程•第k个字符串•解题报告

第k个字符串

题目描述

给定两个整数 n n n k k k

n − 2 n−2 n2 a a a 2 2 2 b b b 来构成一个字符串,则一共可以构成 n ( n − 1 ) / 2 n(n−1)/2 n(n1)/2 个不同的字符串。

将这 n ( n − 1 ) / 2 n(n−1)/2 n(n1)/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 1T103n201k100

60% 测试点满足 3 ≤ n ≤ 100 3≤n≤100 3n100

80% 测试点满足 3 ≤ n ≤ 10000 3≤n≤10000 3n10000

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) 1T100003n1051kmin(2×109n(n1)/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;
}

邓老师:17773008810(岳阳市,途灵编程)
  • 16
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值