洛谷 P5657 [CSP-S2019] 格雷码

【题目链接】

洛谷 P5657 [CSP-S2019] 格雷码

【题目考点】

1. 递归
2. 位运算
3. 格雷码

【解题思路】

解法1:递归

递归问题:求n位格雷码中的第k号二进制串
递归关系
根据题目描述:
n位格雷码

  • 由n-1位格雷码的 2 n − 1 2^{n-1} 2n1个二进制串按顺序排列加前缀0,构成前 2 n − 1 2^{n-1} 2n1个格雷码,编号为 0 ∼ 2 n − 1 − 1 0\sim2^{n-1}-1 02n11
  • 由n-1位格雷码的 2 n − 1 2^{n-1} 2n1个二进制串按逆序排列加前缀1,构成后 2 n − 1 2^{n-1} 2n1个格雷码,编号为 2 n − 1 ∼ 2 n − 1 2^{n-1}\sim2^n-1 2n12n1

要查询的是n位格雷码的第k号二进制串,先比较k与 2 n − 1 2^{n-1} 2n1

  • 如果 k < 2 n − 1 k < 2^{n-1} k<2n1,则查找n-1位格雷码中的第k号二进制串,在前面再加上前缀0,即为n位格雷码的第k号二进制串。
  • 如果 k ≥ 2 n − 1 k \ge 2^{n-1} k2n1,n位格雷码第k号二进制串是编号 2 n − 1 ∼ 2 n − 1 2^{n-1}\sim2^n-1 2n12n1的格雷码中的第 k − 2 n − 1 k-2^{n-1} k2n1个数字(从0开始)。
    该二进制串去掉前缀1后,为在n-1位格雷码的逆序序列中的第 k − 2 n − 1 k-2^{n-1} k2n1个数字

推导对于正向、逆向都从0开始数的长为n的序列,逆向第i个数字是正向第几个数字

逆向正向
0n-1
1n-2
2n-3
n-10
in-1-i

因此,在n-1位格雷码的有 2 n − 1 2^{n-1} 2n1个元素的逆序序列中的第 k − 2 n − 1 k-2^{n-1} k2n1个数字,在正序序列中编号为 2 n − 1 − 1 − ( k − 2 n − 1 ) = 2 n − 1 − k 2^{n-1}-1-(k-2^{n-1})=2^n-1-k 2n11(k2n1)=2n1k
(代码中将该表达式写为 2 n − 1 − k − 1 + 2 n − 1 2^{n-1}-k-1+2^{n-1} 2n1k1+2n1,以避免当n为64时,数据值超过unsigned long long可以表示的范围)
查找n-1位格雷码中的第 2 n − 1 − k 2^n-1-k 2n1k号二进制串,在前面再加上前缀1,即为n位格雷码的第k号二进制串。
递归出口:当n为1时,只有1位格雷码。k为0时,第0号格雷码为"0"。k为1时,第1号格雷码为1。
【注意】:n最大为64,且 k < 2 n k<2^n k<2n,那么k最大为 2 64 − 1 2^{64}-1 2641,应该使用unsigned long long类型来保存k以及二进制串对应的数值。
为了方便得到2的幂,可以先使用递推方法预处理出一个数组保存2的幂。

解法2:二进制转为格雷码

如将k转为二进制数后共有m位,从低位到高位的编号为0, 1, …, m-1。

例:k为13,二进制编码为:1101,
第3位为1,第2位为1,第1位为0,第0位为1

  • 格雷码的第n-1位与二进制数的第n-1位相同
  • i 小于n-1时,如果数值的二进制编码从右边开始数的第 i 位和 i+1 位相同,则对应的格雷码的第 i 位为0,否则为1。
    也就是说:格雷码第i位 = 二进制数第i位 异或 二进制数第i+1位

将数值k右移1位,得到k1。k1的第i位就是k的第i+1位。
将k与k1异或,即可得到格雷码。

【题解代码】

解法1:递归

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;
ULL pow2[70];//pow2[i]:2^i的值 
string solve(ULL n, ULL k)//求n位格雷码中的第k号二进制串
{
	if(n == 1)//1位格雷码 
	{
		if(k == 0)
			return "0";
		else//k == 1 
			return "1"; 
	}
	if(k < pow2[n-1])
		return "0"+solve(n-1, k);//查找n-1位格雷码中的第k号二进制串,在前面再加上前缀0
	else//k >= pow2[n-1]
		return "1"+solve(n-1, pow2[n-1]-k-1+pow2[n-1]);//查找n-1位格雷码中的第2^n-1-k2号二进制串,在前面再加上前缀1
}
void initPow2()//预处理生成2的幂 
{
	pow2[0] = 1;
	for(int i = 1; i <= 63; ++i)
		pow2[i] = pow2[i-1]*2; 
}
int main()
{
	initPow2();
	ULL n, k;
	cin >> n >> k;
	cout << solve(n, k);
	return 0;
}

解法2:二进制转为格雷码

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;
int main()
{
	string s;
	ULL n, k, a;
	cin >> n >> k;
	a = k ^ (k >> 1);
	for(int i = 1; i <= n; ++i)//格雷码必须有n位,不足则前面补0 
	{
		s = char(a%2+'0')+s;
		a /= 2;
	}
	cout << s;
	return 0;
}
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值