2019CSP day1t1 格雷码

题目描述

通常,人们习惯将所有 (n) 位二进制串按照字典序排列,例如所有 (2) 位二进制串按字典序从小到大排列为:(00,01,11,10)

格雷码((Gray Code))是一种特殊的 (n) 位二进制串排列法,它要求相邻的两个二进制串间恰好有一位不同,特别地,第一个串与最后一个串也算作相邻。

所有 (2) 位二进制串按格雷码排列的一个例子为:(00)(01)(11)(10)

(n) 位格雷码不止一种,下面给出其中一种格雷码的生成算法:

  1. (1) 位格雷码由两个 (1) 位二进制串组成,顺序为:(0)(1)
  2. (n + 1) 位格雷码的前 (2^n) 个二进制串,可以由依此算法生成的 (n) 位格雷码(总共 (2^n)(n) 位二进制串)按顺序排列,再在每个串前加一个前缀 (0) 构成。
  3. (n + 1) 位格雷码的后 (2^n) 个二进制串,可以由依此算法生成的 (n) 位格雷码(总共 (2^n)(n) 位二进制串)按逆序排列,再在每个串前加一个前缀 (1) 构成。

综上,(n + 1) 位格雷码,由 (n) 位格雷码的 (2^n)个二进制串按顺序排列再加前缀 (0),和按逆序排列再加前缀 (1) 构成,共 (2^{n+1}) 个二进制串。另外,对于 (n) 位格雷码中的 (2^n)个 二进制串,我们按上述算法得到的排列顺序将它们从 (0 sim 2^n - 1) 编号。

按该算法,(2)位格雷码可以这样推出:

  1. 已知 (1) 位格雷码为 (0)(1)
  2. 前两个格雷码为$ 00$,(01)。后两个格雷码为 (11)(10)。合并得到 (00)(01)(11)(10),编号依次为 (0sim 3)

同理,(3) 位格雷码可以这样推出:

  1. 已知 (2) 位格雷码为:(00)(01)(11)(10)
  2. 前四个格雷码为:(000)(001)(011)(010)。后四个格雷码为:(110)(111)(101)(100)。合并得到:(000)(001)(011)(010)(110)(111)(101)(100),编号依次为 (0sim7)

现在给出 (n)(k),请你求出按上述算法生成的 (n) 位格雷码中的 (k) 号二进制串。

输入格式

仅一行两个整数 (n)(k),意义见题目描述。

输出格式

仅一行一个 (n) 位二进制串表示答案。

输入输出样例

输入 #1

2 3

输出 #1

10

输入 #2

3 5

输出 #2

111

输入 #3

44 1145141919810

输出 #3

00011000111111010000001001001000000001100011

说明/提示

【样例 (1) 解释】

(2) 位格雷码为:(00)(01)(11)(10),编号从 (0sim3),因此 (3) 号串是 (10)

【样例 (2) 解释】

(3) 位格雷码为:(000)(001)(011)(010)(110)(111)(101)(100),编号从 (0sim7),因此 (5) 号串是 (111)

【数据范围】

对于 (50\%) 的数据:(n leq 10)

对于 (80\%) 的数据:(k leq 5 imes 10^6)

对于 (95\%) 的数据:(k leq 2^{63} - 1)

对于 (100\%) 的数据:(1 leq n leq 64), (0 leq k lt 2^n)

这个题正解据说是位运算,但是似乎也不用这么麻烦。然而我考场上并没有开(unsigned) (long) (long)所以我就没了。

考虑按照题意模拟。按照题意,一个(n)位的格雷码是由一个前缀(0)(1)加上一个长度为(n-1)为的格雷码构成的,所以我们可以考虑类似康托展开的方法。

我们不妨先处理出所有(2)的幂。对于我们知道这个长度为(n)的格雷码在当前所有长度为(n)的格雷码中应该正序排第(k)位,则如果(nlt 2^{n-1})(只有小于是因为编号是从(0)开始存的)则当前这一位还放(0),转移到(dfs(n-1,k));反之放下一个(1),考虑怎么处理逆序。

由于我们已经放下了一个(1),所以我们已经整个过滤掉了(2^{n-1})个比它排名靠前的格雷码(因为这些格雷码第一位应该是(0)),所以我们最后处理编号的范围是(2^{n-1}),所以我们首先要把(k)减掉(2^{n-1})。手玩一下可以知道,编号从(0)开始存这个事情非常麻烦,所以我们需要把这个数整个向右面移一位,也就是加上(1)

再考虑要求倒序排列。这个很简单,因为这(2^{n-1})个格雷码的编号是(0sim2^{n-1}-1),所以容易知道我们只需要用(2^{n-1}-(k-2^{n-1}+1))即可。(其实手玩一下或者打表找规律也行。)

上代码。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
#include<cmath>
#define int long long
#define rep(i,a,n) for(register int i=a;i<=n;++i)
#define dep(i,n,a) for(register int i=n;i>=a;--i)
using namespace std;
int n;
unsigned long long k;
unsigned long long num[64];
inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
void write(int x)
{
	if(x<0)putchar('-'),x=-x;
	if(x==0)return;
	write(x/10);
	putchar(x%10+'0');
}
void dfs(int step,int k)
{
	if(step==0)return;
	if(k<num[step-1])
	{
		putchar('0');
		dfs(step-1,k);
	}
	else
	{
		putchar('1');
		dfs(step-1,num[step-1]-(k-num[step-1]+1));
	}
}
signed main()
{
	n=read(),k=read();
	num[0]=1;
	rep(i,1,n-1)num[i]=num[i-1]*2;
	dfs(n,k);
	return 0;
}

一定注意编号必须从(0)存,否则(unsigned) (long) (long)存不下。

原文地址:https://www.cnblogs.com/qxds/p/11883668.html