2019CSP day1t1 格雷碼

題目描述

一般,人們習慣將全部 \(n\) 位二進制串按照字典序排列,例如全部 \(2\) 位二進制串按字典序從小到大排列爲:\(00,01,11,10\)ios

格雷碼(\(Gray Code\))是一種特殊的 \(n\) 位二進制串排列法,它要求相鄰的兩個二進制串間剛好有一位不一樣,特別地,第一個串與最後一個串也算做相鄰。git

全部 \(2\) 位二進制串按格雷碼排列的一個例子爲:\(00\)\(01\)\(11\)\(10\)算法

\(n\) 位格雷碼不止一種,下面給出其中一種格雷碼的生成算法:spa

  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\) 編號。code

按該算法,\(2\)位格雷碼能夠這樣推出:get

  1. 已知 \(1\) 位格雷碼爲 \(0\)\(1\)
  2. 前兩個格雷碼爲$ 00$,\(01\)。後兩個格雷碼爲 \(11\)\(10\)。合併獲得 \(00\)\(01\)\(11\)\(10\),編號依次爲 \(0\sim 3\)

同理,\(3\) 位格雷碼能夠這樣推出:string

  1. 已知 \(2\) 位格雷碼爲:\(00\)\(01\)\(11\)\(10\)
  2. 前四個格雷碼爲:\(000\)\(001\)\(011\)\(010\)。後四個格雷碼爲:\(110\)\(111\)\(101\)\(100\)。合併獲得:\(000\)\(001\)\(011\)\(010\)\(110\)\(111\)\(101\)\(100\),編號依次爲 \(0\sim7\)

如今給出 \(n\)\(k\),請你求出按上述算法生成的 \(n\) 位格雷碼中的 \(k\) 號二進制串。it

輸入格式

僅一行兩個整數 \(n\)\(k\),意義見題目描述。io

輸出格式

僅一行一個 \(n\) 位二進制串表示答案。class

輸入輸出樣例

輸入 #1

2 3

輸出 #1

10

輸入 #2

3 5

輸出 #2

111

輸入 #3

44 1145141919810

輸出 #3

00011000111111010000001001001000000001100011

說明/提示

【樣例 \(1\) 解釋】

\(2\) 位格雷碼爲:\(00\)\(01\)\(11\)\(10\),編號從 \(0\sim3\),所以 \(3\) 號串是 \(10\)

【樣例 \(2\) 解釋】

\(3\) 位格雷碼爲:\(000\)\(001\)\(011\)\(010\)\(110\)\(111\)\(101\)\(100\),編號從 \(0\sim7\),所以 \(5\) 號串是 \(111\)

【數據範圍】

對於 \(50\%\) 的數據:\(n \leq 10\)

對於 \(80\%\) 的數據:\(k \leq 5 \times 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\)位,則若是\(n\lt 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}\)個格雷碼的編號是\(0\sim2^{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\)存不下。

相關文章
相關標籤/搜索