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

题目信息

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

在线评测地址:Luogu#2328

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

题目描述

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

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

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

输入格式

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

输出格式

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

数据规模与约定

\(2\le B\le 36\)\(1\le 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('\n');

	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('\n');
	}

	return 0;
}
posted @ 2020-09-13 14:50  5ab  阅读(109)  评论(0编辑  收藏  举报