【题解】[SCOI2005] 超级格雷码 By 5ab as a juruo.

题目信息

题目来源:CCF 四川省选 2005;

在线评测地址:Luogu#2328

运行限制:时间不超过 (1.00 extrm{s}),空间不超过 (128 extrm{MiB})

题目描述

著名的格雷码是指 (2^n) 个不同 (n) 位二进制数(即 (0)~(2^n-1),不足 (n) 位在前补零)的一个排列,这个排列满足相邻的两个二进制数的 (n) 位数字中最多只有一个数字不同(例如 ( exttt{003})( exttt{001}) 就有一个数位不同,而 ( exttt{003})( exttt{030}) 有两个数位不同,不符合条件)。例如 (n=2) 时,(( exttt{00}, exttt{01}, exttt{11}, exttt{10})) 就是一个满足条件的格雷码。

所谓超级格雷码就是指 (B^n) 个不同的 (n)(B) 进制数的排列满足上面的条件。

任务:给出 (n)(B),求一个满足条件的格雷码。对于大于 (9) 的数用 ( exttt{A})~( exttt{Z}) 表示。(分别表示 (10)~(35)

输入格式

只有一行,为两个整数 (n)(B)

输出格式

一共 (B^n) 行,每行一个 (B) 进制数,表示你所求得的符合条件的排列。

数据规模与约定

(2le Ble 36)(1le B^n< 2^{16}=65536)

分析

我们要将二进制拓展到 (B) 进制。

如果你连二进制怎么做都不知道,出门左转慢走不谢。

按照题目中的意思,易证这种生成方法满足格雷码性质。我们可以凑出一个类似的做法:

  • (1) 位超级格雷码有 (0,1,2,cdots,B-1)(B) 个。
  • (N) 位格雷码由如下算法生成:
    • 对于第 (2k) 种,在前面加上 (2k-1),后面接上逆序(N-1) 位超级格雷码;
    • 对于第 (2k+1) 种,在前面加上 (2k),后面接上正序(N-1) 位超级格雷码。

我们也可以用证明二进制的方法来证明这个是正确的。这样我们就可以做出来这道题,获得 (100) 分的好成绩。


当然,我们完全有更好的方法,可以在 (mathcal{O}(1)) 的时间内进行转移。即——利用 lowbit 函数。

我们可以观察到,最后一位变化的时间,恰好是标号增加到 (B^{n-1}) 的时候,即 (operatorname{lowbit}_B(t)=B^{n-1})

所以,我们计算一个 (B) 进制的 lowbit,就可以完成这个优化。如果不是因为输出的原因,上面那个做法就应该错了。

注意事项

  • 进行 lowbit 操作时,标号应当从 (0) 开始而不是 (1)
  • 如果使用后者进行转移,不要忘记在开始时特判;
  • 别忘了这道题奇异的输出方式。

Code

#include <cstdio>
using namespace std;

int lowbit(int n, int b)
{
	int x = 1, t = 0;

	while (!(n % (x * b)))
		x *= b, t++;
	
	return t;
}

int bts[16] = {};

int main()
{
	int n, b, t = 1;

	scanf("%d%d", &n, &b);
	for (int i = 0; i < n; i++)
		t *= b;
	
	for (int j = 0; j < n; j++)
		printf("%c", (bts[j] < 10)? (bts[j] + '0'):(bts[j] - 10 + 'A'));
	putchar('
');

	for (int i = 1; i < t; i++)
	{
		bts[lowbit(i, b)] = (bts[lowbit(i, b)] + 1) % b;
		
		for (int j = 0; j < n; j++)
			printf("%c", (bts[j] < 10)? (bts[j] + '0'):(bts[j] - 10 + 'A'));
		putchar('
');
	}

	return 0;
}
原文地址:https://www.cnblogs.com/5ab-juruo/p/solution-scoi2005_lg2328.html