UVa 11979 Hamming Base

题目描述

给定 NNNNNN 进制整数,每个整数恰好有 MMM 位(可能有前导零)。 如果两个整数在恰好 KKK 个位置上具有相同的数字,则称它们为 KKK -相似的。 例如:

  • 321321321213213213000-相似的。
  • 345634563456645364536453222-相似的。
  • 123123123453453453111-相似的。

你需要改变这些给定的 NNN 个整数,使得每一对整数都是 0-相似的。 为达成目标,你可以分多步进行改变。 在一步中,你可以将一个整数的某一个数字加 111 或减 111(即递增或递减)。 但是不能将 0 递减,也不能将数字 N−1N-1N1 递增。

你需要用最少的步数达成目标。

输入格式

输入以一个整数 TTT≤50\leq 5050 )开始,表示测试用例的数量。 每个测试用例以一行包含两个整数 NNN2≤N≤20002 \leq N \leq 20002N2000 )和 MMM1≤M≤101 \leq M \leq 101M10 )开始。 接下来的 NNN 行,每行包含 MMM 个在 000N−1N-1N1 之间的整数,表示一个 NNN 进制下的 MMM 位数。

输出格式

对于每个测试用例,输出用例编号和达成目标所需的最少步数。

题目分析

目标状态分析

题目要求最终所有整数对都是 0−0-0 相似的,这意味着任意两个整数在 所有 位置上数字都不相同。 我们考虑其中一个位置 jjj1≤j≤M1 \leq j \leq M1jM ),该位置上有 NNN 个数字(因为有 NNN 个整数)。 要使它们两两不同,并且每个数字的取值范围是 000N−1N-1N1 ,那么该位置上 NNN 个数字必须恰好是 0,1,…,N−10, 1, \dots, N-10,1,,N1 的一个排列。 换句话说,对于每个位置,最终状态必须是 000N−1N-1N1 每个数字恰好出现一次。

操作分析

允许的操作是:将一个数字加 111 或减 111,但不能越界(000 不能减, N−1N-1N1 不能加)。 因此将一个数字从 aaa 改为 bbb 的最小步数是 ∣a−b∣|a-b|ab ,因为每次只能改变 111,且方向不受限制(只要不越界),所以可以直接直线移动。

问题分解

原问题可以分解为 MMM 个独立的子问题,每个子问题对应一个位置:
给定 NNN 个数字(可能重复),要将它们改为 0,1,…,N−10, 1, \dots, N-10,1,,N1 的一个排列,使得总修改步数最小。

对于每个位置,我们需要:

  1. 将当前 NNN 个数字与目标 NNN 个数字(即 0,1,…,N−10, 1, \dots, N-10,1,,N1 )进行配对。
  2. 使得配对后的总绝对差之和最小。

这实际上是一个指派问题Assignment Problem\texttt{Assignment Problem}Assignment Problem),但 NNN 最多可达 200020002000MMM 最多为 101010 ,如果直接使用 O(N3)O(N^3)O(N3) 的匈牙利算法会超时。 因此需要寻找更优的方法。

最优匹配策略

注意到目标数字是固定的、有序的序列 0,1,…,N−10, 1, \dots, N-10,1,,N1 。 对于一维欧几里得距离(即绝对差),一个经典结论是:将当前数字排序后,按顺序匹配到有序的目标数字上,可以得到最小总距离。

证明简述
设当前数字为 a1≤a2≤⋯≤aNa_1 \leq a_2 \leq \dots \leq a_Na1a2aN ,目标数字为 b1≤b2≤⋯≤bNb_1 \leq b_2 \leq \dots \leq b_Nb1b2bN 。 我们需要找到一个排列 σ\sigmaσ 使得 ∑i=1N∣ai−bσ(i)∣\sum_{i=1}^N |a_i - b_{\sigma(i)}|i=1Naibσ(i) 最小。 根据排序不等式,对于绝对值距离,顺序和(即 aia_iaibib_ibi 配对)是最小的。 这是因为任意交换两个配对都会导致总距离增加或不变。

因此,对于每个位置:

  1. 读取该位置上的 NNN 个数字,存入数组。
  2. 对该数组排序。
  3. 计算总代价: ∑i=0N−1∣sorted[i]−i∣\sum_{i=0}^{N-1} |\text{sorted}[i] - i|i=0N1sorted[i]i ,其中 iii 是目标数字。

算法步骤

  1. 读入 TTT 个测试用例。
  2. 对于每个测试用例:
    • 读入 NNNMMM ,然后读入 N×MN \times MN×M 的数字矩阵。
    • 初始化总步数 totalSteps = 0
    • 对于每个位置 jjj0≤j<M0 \leq j < M0j<M ):
      • 提取该位置上所有 NNN 个数字,存入数组 currentDigits
      • currentDigits 排序。
      • 遍历排序后的数组,计算 ∑i=0N−1∣currentDigits[i]−i∣\sum_{i=0}^{N-1} |\texttt{currentDigits}[i] - i|i=0N1currentDigits[i]i ,累加到 totalSteps
    • 输出 totalSteps

复杂度分析

  • 对于每个位置,排序需要 O(Nlog⁡N)O(N \log N)O(NlogN) ,计算代价需要 O(N)O(N)O(N)
  • 总共有 MMM 个位置,因此每个测试用例的时间复杂度为 O(M⋅Nlog⁡N)O(M \cdot N \log N)O(MNlogN)
  • N≤2000N \leq 2000N2000M≤10M \leq 10M10 的条件下,完全可以在时限内完成。

代码实现

// Hamming Base
// UVa ID: 11979
// Verdict: Accepted
// Submission Date: 2025-12-05
// UVa Run Time: 0.020s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net

#include <bits/stdc++.h>
using namespace std;

int main() {
    int testCases;
    cin >> testCases;
    for (int caseNo = 1; caseNo <= testCases; caseNo++) {
        int n, m;
        cin >> n >> m;
        vector<vector<int>> digits(n, vector<int>(m));
        for (int i = 0; i < n; i++)
            for (int j = 0; j < m; j++)
                cin >> digits[i][j];
        long long totalSteps = 0;
        // 对每个位置独立处理
        for (int pos = 0; pos < m; pos++) {
            vector<int> currentDigits(n);
            for (int i = 0; i < n; i++)
                currentDigits[i] = digits[i][pos];
            sort(currentDigits.begin(), currentDigits.end());
            // 排序后一一匹配到 0,1,...,n-1
            for (int i = 0; i < n; i++)
                totalSteps += abs(currentDigits[i] - i);
        }
        cout << "Case " << caseNo << ": " << totalSteps << endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值