题解:洛谷 P1461 海明码
【题目来源】
洛谷:P1461 [USACO2.1] 海明码 Hamming Codes - 洛谷
【题目描述】
给出 n,b,d,要求找出 n 个由 0,10,1 组成的编码,每个编码有 b 位),使得两两编码之间至少有 d 个单位的 “Hamming距离”。“Hamming距离”是指对于两个编码,他们二进制表示法中的不同二进制位的数目。看下面的两个编码 0x554 和 0x234(十六进制数)
0x554 = 0101 0101 0100
0x234 = 0010 0011 0100
不同位 xxx xx
因为有五个位不同,所以“Hamming距离”是 5。
【输入】
一行,包括 n,b,d。
【输出】
n 个编码(用十进制表示),要排序,十个一行。如果有多解,你的程序要输出这样的解:假如把它化为 2^b 进制数,它的值要最小。
【输入样例】
16 7 3
【输出样例】
0 7 25 30 42 45 51 52 75 76
82 85 97 102 120 127
【核心思想】
-
问题分析:给定 \(n, b, d\),要求找出 \(n\) 个 \(b\) 位二进制编码(\(0 \sim 2^b-1\)),使得任意两个编码的汉明距离 \(\ge d\)。输出按十进制升序排列,十个一行。若有多解,要求化为 \(2^b\) 进制时值最小(即十进制值最小)。
-
算法选择:
- 贪心构造:从小到大枚举候选数,每次选择第一个与已选所有数汉明距离均 \(\ge d\) 的数
- 位运算计算汉明距离:逐位比较两个数的二进制位,统计不同位的数量
-
关键步骤:
- 读入:\(n\)(编码数量)、\(b\)(位数)、\(d\)(最小汉明距离)
- 贪心构造编码(\(i\) 从 \(1\) 到 \(n\)):
- 枚举候选数 \(j\) 从 \(0\) 到 \(2^b-1\)
- 对当前候选 \(j\),遍历已选编码 \(a[1..i-1]\):
- 计算
hamming(a[k], j, b),若存在 \(< d\) 的,标记flag = 1并跳出
- 计算
- 若
flag == 0(与所有已选编码距离均 \(\ge d\)):a[i] = j,跳出内层循环,继续构造下一个编码
- 输出:按十个一行的格式输出所有编码
-
时间/空间复杂度:
- 时间复杂度:\(O(n \cdot 2^b \cdot n \cdot b) = O(n^2 \cdot b \cdot 2^b)\),实际因贪心提前终止远小于理论值
- 空间复杂度:\(O(n)\),存储选中的编码
-
贪心构造的核心思想:
- 从小到大枚举保证最小性:从 \(0\) 开始依次尝试候选数,第一个满足条件的数必然是最小的,从而保证最终序列字典序最小
- 汉明距离逐位计算:利用位运算
&1取最低位,>>=1右移,逐位比较统计不同位数 - 增量式验证:新候选只需与已选编码比较,无需考虑未选编码
- 贪心最优性:该问题中贪心策略能得到最优解,因为编码空间足够大(\(2^b\)),且从小到大选择不会阻塞后续选择
- 适用于编码构造、距离约束、贪心选择类问题
【解题思路】

【算法标签】
普及 #位运算
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n, b, d, a[70];
int hamming(int x, int y, int b)
{
int cnt = 0; // 定义计数器
for (int i=1; i<=b; i++) {
int xx = x&1, yy = y&1; // 比较最后一位,如7&1==1,9&1==1
if (xx!=yy) cnt++; // 如果最后一位不相同,则不同数量加1
x>>=1; // xx和yy都向右移一位,下轮仍然做最后一位的比较
y>>=1;
}
// cout << "cnt " << cnt << endl;
return cnt; // 返回所有位比较后不相同的位数
}
int main()
{
cin >> n >> b >> d; // 输入n、b和d
for (int i=1; i<=n; i++) { // 要找到n个数
for (int j=0; j<1<<(b+1)-1; j++) { // 数在0到1<<b的范围内,如b=7,数的范围为0-255
int flag = 0; // 定义标记
for (int k=1; k<i; k++) { // 题目要求与其他所有的数相比
if (hamming(a[k], j, b)<d) { // 只要遇到一个不符合要求的
flag = 1; // 修改标记位
break; // 退出循环
}
}
if (!flag) { // 如果都满足要求
a[i] = j; // 将j这个数赋值给a[i]
break; // 退出循环,继续下轮查找
}
}
}
for (int i=1; i<=n; i++) { // 遍历a数组中的所有数
cout << a[i] << " "; // 依次输出
if (i%10==0) cout << endl; // 遇到i为10的倍数,就输出换行
}
cout << endl;
return 0;
}
【运行结果】
16 7 3
0 7 25 30 42 45 51 52 75 76
82 85 97 102 120 127
浙公网安备 33010602011771号