题解:洛谷 P1461 海明码

【题目来源】

洛谷:P1461 [USACO2.1] 海明码 Hamming Codes - 洛谷

【题目描述】

给出 n,b,d,要求找出 n 个由 0,10,1 组成的编码,每个编码有 b 位),使得两两编码之间至少有 d 个单位的 “Hamming距离”。“Hamming距离”是指对于两个编码,他们二进制表示法中的不同二进制位的数目。看下面的两个编码 0x5540x234(十六进制数)

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

【核心思想】

  1. 问题分析:给定 \(n, b, d\),要求找出 \(n\)\(b\) 位二进制编码(\(0 \sim 2^b-1\)),使得任意两个编码的汉明距离 \(\ge d\)。输出按十进制升序排列,十个一行。若有多解,要求化为 \(2^b\) 进制时值最小(即十进制值最小)。

  2. 算法选择

    • 贪心构造:从小到大枚举候选数,每次选择第一个与已选所有数汉明距离均 \(\ge d\) 的数
    • 位运算计算汉明距离:逐位比较两个数的二进制位,统计不同位的数量
  3. 关键步骤

    • 读入\(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,跳出内层循环,继续构造下一个编码
    • 输出:按十个一行的格式输出所有编码
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \cdot 2^b \cdot n \cdot b) = O(n^2 \cdot b \cdot 2^b)\),实际因贪心提前终止远小于理论值
    • 空间复杂度:\(O(n)\),存储选中的编码
  5. 贪心构造的核心思想

    • 从小到大枚举保证最小性:从 \(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
posted @ 2026-08-03 08:47  团爸讲算法  阅读(8)  评论(0)    收藏  举报