1. 程式人生 > 實用技巧 >NOIP2019 格雷碼 [提高組]

NOIP2019 格雷碼 [提高組]

題目:格雷碼

網址:https://www.luogu.com.cn/problem/P5657

通常,人們習慣將所有\(n\)位二進位制串按照字典序排列,例如所有\(2\)位二進位制串按字典序從小到大排列為:\(00,01,10,11\)

格雷碼(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∼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\)位二進位制串表示答案。

輸入輸出樣例

輸入

2 3

輸出

10

輸入 #2

3 5

輸出 #2

111

輸入 #3

44 1145141919810

輸出 #3

00011000111111010000001001001000000001100011

說明/提示

【樣例\(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%\)的資料:\(0≤n≤10\)
對於\(80%\)的資料:\(k≤5×10^6\)
對於\(95%\)的資料:\(k≤2^{63}\)
對於\(100%\)的資料:\(1≤n≤64,0≤k<2^n\)


如果模擬,那麼只能過\(50\)分。

首先,確定第\(k\)個數的位置在哪裡。若\(k<2^n\),該數在序列前半部分;反之,則在右半部分。
該數的第\(n\)位就是如此確定下來的:若在前半部分,第\(n\)位數上為\(0\);若為後半部分,第\(n\)位數上為\(1\)

我們繼續。

若該數位於前半部分,那麼\(n-1\)位即可以按上述規律確定;如果不幸在後面了,\(n-1\)位規律是相反的。

分治!

換句話說,確定第\(i+2\)位數之後,該數的位置僅影響的是第\(i+1\)位的確定,但跟第\(i\)位毫無關聯。

程式碼如下:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define ull unsigned long long
using namespace std;
const int maxn = 64 + 5;
ull n, k, p[maxn];
int main()
{
	cin >> n >> k;
	p[0] = 1;
	for(int i = 1; i <= n; ++ i) p[i] = p[i - 1] << 1ll;
	int cur = 0;
	for(int i = n; i > 0; -- i)
	{
		if(!cur)
		{
			if(k < p[i - 1]) 
			{
				putchar('0');
				cur = 0;
			}
			else 
			{
				putchar('1');
				cur = 1;
			}
		}
		else
		{
			if(k < p[i - 1]) 
			{
				putchar('1');
				cur = 0;
			}
			else 
			{
				putchar('0');
				cur = 1;
			}
		}
		k %= p[i - 1];
	}
	return 0;
}