信息学奥赛复赛复习20-CSP-S2019-01格雷码-数据类型范围、unsigned 关键字、无符号范围、递归算法

PDF文档公众号回复关键字:20241023

1 P5657 [CSP-S2019] 格雷码

[题目描述]

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

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

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

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

1 位格雷码由两个 1 位二进制串组成,顺序为:0,1。

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

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

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

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

1已知 1 位格雷码为 0,1。
2前两个格雷码为 00,01。后两个格雷码为 11,10。合并得到 00,01,11,10,编号依次为 0 ~ 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,编号依次为 0 ~ 7。

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

[输入格式]

仅一行两个整数 n,k

[输出格式]

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

[输入输出样例]

输入 #1

2 3

输出 #1

10

输入 #2

3 5

输出 #2

111

说明/提示

【样例 1 解释】

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

【样例 2 解释】

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

数据规模

对于 50% 的数据:n≤10

对于 80% 的数据:k≤5×10^6

对于 95% 的数据:k≤26^3−1

对于 100% 的数据:1≤n≤64, 0≤k<2^n

2 相关知识点

1) 数据类型

数据类型 描述 取值范围
char 字符型 -128 到 127 或 0 到 255(取决于是否是有符号的)
short 短整型 -32,768 到 32,767
int 整型 -2,147,483,648 到 2,147,483,647
long 长整型 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
long long 更长的整型 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
unsigned long long 无符号 0到18446744073709551615

2) unsigned 关键字

用于声明无符号整数类型。无符号整数类型只能表示非负整数,即它们的值总是大于或等于零

例如

short是16为二进制组成,第1位是符号位,表示范围-32768~32767之间
unsigned short是16为二进制组成,无符号位, 表示范围0~65535之间

#include<bits/stdc++.h>
using namespace std;
/*
 无符号关键字
 short是16为二进制组成,第1位是符号位,表示范围-32768~32767之间
 unsigned short是16为二进制组成,无符号位, 表示范围0~65535之间
*/ 
int main(){
	short a=32769;//超出了short的范围 
	unsigned short b=32769;//在范围内可以正常表示 
	unsigned long long ull= ~0ULL; 
	cout<<"a的值为:"<<a<<endl; //输出不正确 
	cout<<"b的值为:"<<b<<endl;
	cout<<"ull的值为:"<<ull<<endl;
	return 0;
}
/* 
a的值为:-32767 
b的值为:32769
ull的值为:18446744073709551615
*/

3) 递归算法

递归就是函数自己直接或者间接的调用自己

在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法

递归2重要概念

递归式-递归函数

将原问题分为若干规模较小、相互独立、与原问题形式相同或相似的子问题

比如:斐波那契数列

递归式

F(n)=F(n - 1)+F(n - 2)

递归边界

递归边界则是分解的尽头,如果递归式不断递归而不进行阻止,那么最后将进入无穷尽的死循环,将无法解决问题

比如:斐波那契数列

递归到

F(0)=1 F(1)=1

斐波那契数列

斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,
指的是这样一个数列:1、1、2、3、5、8、13、21、34、……
在数学上,斐波纳契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(3)=2,F(n)=F(n-1)+F(n-2)(n>=4,n∈N)

分析

定义函数实现求第几项斐波那契数

int fib(int n)

入参 n表示第几项

出参 返回第n项斐波那契数

实现

递归式

fib(n)=fib(n-1)+fib(n-2)

递归出口

n=1 或 n=2

参考代码

#include<bits/stdc++.h>
using namespace std;
//求 第n项斐波那契数列数
int fib(int n){
	if(n==1 || n==2){//前2项直接返回
		return 1;
	}
	return fib(n-1)+fib(n-2);//第3项=前两项之和
}

int n;
int main(){
	cin>>n;
	cout<<fib(n)<<endl;
}

时间复杂度

递归求斐波那契数列时间复杂度:O(2^n)

3 思路分析

思路1-递归

1 定义递归函数,求n位格雷码的第k位
2 递归出口,如果n=1,即为1位格雷码时,输出第0位为0,第1位为1
3 前半部分输出0,递归直接求n-1位格雷码的第k位
4 后半部分输出1,对k进行处理,需要从右向左数k转变为k1,递归求n-1位格雷码的第k1位

示例程序

#include <iostream>
#include <cstdio>
using namespace std;
typedef unsigned long long ULL;
int n;
ULL k;
/*
  求n位格雷码的第k个码 
  n位 格雷码
  第k个码 
*/
void f (int n,ULL k) {
    if (n == 1) {//递归出口 如果n为1时 
        if (k) cout << "1";//第1位为1 
        else cout << "0";//第0位为0 
        return ;//退出程序 
    }
    /*
	格雷码数/2,实际计算对半数进行处理 
	2位格雷码,2
	3位格雷码,4 
	*/
    ULL x = (1ull << n - 1); 
    if (k < x) {//k在左边 ,k从0开始,相等时在右边 
        cout << "0";//输出0 
        f (n - 1,k);//递归求n-1位,第k个格雷码 
    }
    else {//k在右边 
        cout << "1";//右边输出1 
        ULL k1=(x << 1) - k - 1;//右边顺序从最右到左0 1 2 3 
        f (n - 1,k1);//递归求n-1位,第k1个格雷码 
    }
}
int main () {
    cin >> n >> k;//输入 n k 
    f (n,k);//求n位格雷码的第k个码 
    return 0;
}

思路2-循环

1 对第k个格雷码,从左到右循环输出n位格雷码的每1位
2 每1位输出时,左半部分为0,右半部分为1
3 右半部分和左半部分顺序相反,需要按从右到左计算k的值
k = d * 2 - k - 1

示例程序

#include <iostream>
using namespace std;
typedef unsigned long long ULL;
int n;//n位格雷码 
ULL k;//第k个串 
/*
1位 0 1
2位 00 01 11 10
3位 000 001 011 010 110 111 101 100

找规律,
前半部分为0 
每增加1位前半部分输出0后半部分输出1, 
例如
2 3 
对应格雷码  00 01 11 10
3在右半部分,输出1
继续只关注右半部分 11 10
去除1位 1 0
3为最后1位,对应为0 
(通过公式计算从右边往左的位置 k=d*2-k-1=2*2-3-1=0 
匹配左边为0,右边为1规则,输出0)
*/
int main() {
    cin >> n >> k;//输入 n和k
    ULL d = 1ull << n - 1;//计算格雷码所有串的一半
    while (n) {//前半部分输出0 后半部分输出1 
        if (k < d) cout << "0";//第k位,在前半部分,从0开始小于总数的一半 
        else {//第k位在后半部分 
            cout << "1";//后半部分输出1 
            k = d * 2 - k - 1;//改变k的值,从右边到左0 1 2 3 ,适用左边为0 右边为1的规则 
        }
        n--;//位数减1 
        d >>= 1;//码数减半 
    }
    return 0;
}
posted @ 2024-10-23 17:15  new-code  阅读(26)  评论(0编辑  收藏  举报