两两匹配的最大匹配数——匈牙利算法

文章介绍了如何使用匈牙利算法解决素数伴侣问题,即找出给定正整数中能组成最多素数和的配对。通过构建奇偶数关系矩阵,遍历奇数并尝试匹配偶数,动态调整匹配以达到最佳方案,确保每个数最多形成一个配对。这种方法可用于通信加密等领域。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

若两个正整数的和为素数,则这两个正整数称之为“素数伴侣”,如2和5、6和13,它们能应用于通信加密。现在密码学会请你设计一个程序,从已有的 N ( N 为偶数)个正整数中挑选出若干对组成“素数伴侣”,挑选方案多种多样,

例如有4个正整数:2,5,6,13,如果将5和6分为一组中只能得到一组“素数伴侣”,而将2和5、6和13编组将得到两组“素数伴侣”,能组成“素数伴侣”最多的方案称为“最佳方案”,当然密码学会希望你寻找出“最佳方案”。

输入:

有一个正偶数 n ,表示待挑选的自然数的个数。后面给出 n 个具体的数字。

输出:

输出一个整数 K ,表示你求得的“最佳方案”组成“素数伴侣”的对数。

数据范围:1≤n≤100 ,输入的数据大小满足2≤val≤30000

输入描述:

输入说明1 输入一个正偶数n

2 输入 n 个整数

输出描述:

求得的“最佳方案”组成“素数伴侣”的对数。

示例1

输入:

4

2 5 6 13

输出:2

示例2

输入:

2

3 6

输出:0
转自牛客网_

针对这种求两两最大配对数的类型题目,通用解法就是匈牙利算法,该算法是1955年,库恩(W.W.Kuhn)利用匈牙利数学家康尼格(D.Kőnig)的一个定理构造了这个解法,故称为匈牙利法。具体大家可以在网上搜索到更加详细的信息。

匈牙利算法的核心就是:先到先得,能让则让;

image.png

分析

如图针对左侧ABCD 去匹配右侧abcd,其中A可匹配a,b,记作A->a,b同理可得:

B->a,c

C->a,b

D->c

我们现在要求只能两两配对,比如A虽然可以配对a和b,但是法律不允许“重婚“,所以A只能有一个配偶,基于”先到先得”的原则。A先和a匹配,结合为Aa;

而如果a已经和A配对,当B来和a配对时,如果发现a已经配对了A,那么此时再检查a原来配对的A是否还有其他心仪对象,发现A还可以和b配对;那么不好意思,基于能让则让的原则此时需要拆散原来Aa的匹配,改为Ab匹配,然后既然此时Aa匹配已经拆掉了,那么Ba匹配就可以成立了;

此时已经建立的匹配就是

Ab

Ba

以此类推,当C来和a匹配的时候,发现a已经匹配B,那么在检查B是否还有其他心仪对象,发现B还可以匹配c,所以B让出a然后去匹配c,也就是拆除Ba->重组Bc。 然后C和a组成Ca,此时配对如下:

Ab

Bc

Ca

那么可以类推D来匹配c的时候,会尝试拆除Bc,让B再去查找其他心仪对象,但是此时B除了c之外只能和a匹配,不过a已经匹配了C了,在往下C只能和b匹配,但是b已经匹配了A了,此时我们找遍了c,a,b,都没能够找到能重新建立的匹配,虽然剩下d但是d不是D甚至不是左侧任何人的“心仪对象“,所以此次匹配宣告失败,D无匹配。所以得到匹配的组合为:

Ab

Bc

Ca

最多3对,对于D只能说声”抱歉了!“

回到题目

我们回到素数伴侣的题目上来,如果两个数字的和是一个素数,那么这两个数字就是一对素数伴侣。可以此为依据,构建各个数字能否成为”伴侣“的关系网格矩阵;

构建匹配关系矩阵

而要像两个数之和为素数,那么他们的和就不能是一个大于2的偶数,其和只能是奇数才有可能,比如:2,5,6,13

分成奇数偶数两组:

奇数odds = [5,13]

偶数evens = [2,6]

网格如下:

image

所以构建的可建立关系的网格T如下:

T[i][j]标识为True则表示x[i]和y[j]可以匹配,如[2,5],[6,5],[6,13]

image

我们从横向奇数出发,遍历横向的每个元素,针对每个元素去寻找他的伴侣,也就是在纵向的偶数中去找。
##记录能建立的匹配
寻找的过程中用一个数组evens_match来记录偶数匹配的伴侣,该数组长度和evens数组一样;

对于evens_match组数,其所有的元素都初始化为-1;

evens_match数组使用下标序号作为数字的唯一识别,比如[6,5]这一组匹配:

偶数evens = [2,6]

奇数odds = [5,13]

偶数6的下标在evens中是1;奇树5的下标在odds中是0;

所以对于evens_match可以记录[6,5]这一组匹配为 evens_match[1]=0,就是下标为1的偶数其对应匹配的是下标为0的奇数,这个”下标“奇数偶数在其各自odds和evens数组中的序号;
#记录寻找过程中的足迹
而每一个奇数去匹配之前,我们初始化一个数组evens_visited,这个evens_visited作用是作为一个我们访问过哪些偶数元素的一个记录,其数组长度和evens数组一样;

evens_visited的所有元素初始化为False,当每访问过一个偶数元素后,我们就在evens_visited数组中,同偶数下标的元素设置为True, 比如[6,5]这一组,在找奇数5的匹配时,如果发现6没有访问过,而6在偶数数组evens中的下标是1,那么evens_visited[1] = True 表示在evens数组中下标为1的元素已访问。

上代码

直接上code(py):


def is_primer(v1,v2):
    # 判断v1+v2的结果是否素数
    n = v1+v2
    if n == 1 or n ==2:
        return True
    for v in range(2, n // 2 + 1):
        r, m = divmod(n, v)
        if r > 0 and m == 0:
            return False
    return True


def main():
    quantity = int(input().strip())
    nums = list(map(int,input().strip().split()))
    
    # 奇数偶数分组
    odds,evens = [],[]
    for each in nums:
        if each %2 == 0:
            evens.append(each)
        else:
            odds.append(each)

    # arr作为奇偶匹配关系的网格矩阵
    arr = [[False for _ in range(len(evens))] for _ in range(len(odds))]
    for i in range(len(arr)):
        for j in range(len(arr[i])):
            arr[i][j] = is_primer(odds[i],evens[j])

    # print(arr)

    evens_match = [-1 for _ in range(len(evens))]
    def match(oi):
        # oi是奇数下标
        # 对于每个奇数,迭代偶数数组去找能和其匹配的偶数
        for j in range(len(evens)):
            # 如果奇偶之间的匹配关系存在,也就是奇偶之和是素数;并且本次找到的偶数之前没访问过
            if arr[oi][j] and not evens_visited[j]:
                # 标记该偶数已访问过
                evens_visited[j] = True
                # 检查偶数是否之前没有匹配过
                # 或者当前的偶数如果匹配过,那么其对应匹配的奇数是否能重新建立其他匹配
                if evens_match[j] == -1 or match(evens_match[j]):
                    # 标记当前偶数匹配的奇数,以其在odds数组中的序号下标作为记号,记录匹配的映射关系
                    evens_match[j] = oi
                    return True
        # 如果所有的偶数都找完了都没有匹配到,返回False
        return False

    cnt = 0
    for i in range(len(odds)):
        # 每开始一个新的奇数的匹配查找都重新初始化evens_visited
        evens_visited = [False for _ in range(len(evens))]
        if match(i):
            # 如果下标i对应的奇数能找到偶数匹配那么计数cnt加1
            cnt += 1
    print(cnt)


if __name__ == '__main__':
    main()

每轮匹配都是新的过程

可能有朋友不理解为什么每次对一个新的奇数去找匹配的偶数的时候都要重新初始化evens_visited.这个其实可以这么看,如果evens_visited 始终都是单独一份公共的记录,那么某一个奇数在查找匹配的时候,他可能会访问很多的偶数,但是访问过不代表这个偶数就能和其匹配成功。

在match函数中除了判别匹配关系arr[oi][j]之外还要判别偶数是否已访问,如果偶数在本轮奇数的匹配查找中已经访问过那么不会在尝试去匹配,这是正常的逻辑,但是如果这个访问过的标记一直存在,那么当下一个奇数开启新一轮匹配的时候,它无法找到这个偶数,即使这个偶数可能在之前某次匹配循环过程中已经断开某个匹配变成了未配对的偶数,也无法被访问到。

偶数能不能匹配在match函数中有两个if判断,一层是判断arr[io][j]能够建立匹配,以及偶数未访问过,另外一层是偶数未配对或者偶数已配对但是其之前的匹配对象,也就是奇数对象能断开并重新建立其他新匹配;只有满足这两个条件才能使本轮的奇数配对成功。

### 学生配对问题的算法概述 学生配对问题是计算机科学和运筹学中的经典优化问题之一,其目标通常是基于某些条件(如偏好、关系好坏等),将一组学生两两匹配成最佳组合。这类问题可以被建模为图论中的最大匹配问题或二分图匹配问题。 #### 基于权重的学生配对模型 如果学生的配对依赖于某种评分机制(例如“好”或“坏”的关系可以用数值表示),则该问题可以通过加权完全图来解决。每对学生之间的关系都可以用边上的权重表示,其中正数代表正面关系,负数可能代表负面关系[^1]。 在这种情况下,匈牙利算法是一种有效的解决方案。它能够在多项式时间内找到具有最大总权重的完美匹配。对于 $n$ 名学生的情况,时间复杂度通常为 $O(n^3)$[^2]。 以下是实现匈牙利算法的一个简单 Python 示例: ```python import numpy as np from scipy.optimize import linear_sum_assignment def student_pairing(cost_matrix): row_ind, col_ind = linear_sum_assignment(cost_matrix, maximize=True) total_weight = cost_matrix[row_ind, col_ind].sum() pairs = list(zip(row_ind, col_ind)) return pairs, total_weight # 示例矩阵:正值表示良好关系,负值表示不良关系 cost_matrix = np.array([ [0, 5, -2], [5, 0, 3], [-2, 3, 0] ]) pairs, total_weight = student_pairing(cost_matrix) print(f"最优配对方案: {pairs}") print(f"总权重: {total_weight}") ``` 上述代码利用 `scipy` 库实现了线性指派问题的最大化版本。通过调整输入的成本矩阵,可以根据具体需求定义不同的配对策略[^3]。 #### 非加权情况下的学生配对 当不需要考虑权重而仅需满足基本约束时,可采用更简单的贪心算法或者稳定婚姻算法 (Stable Marriage Algorithm) 的变体。这种场景下,假设每位学生都有一份优先级列表,则 Gale-Shapley 算法能够确保得到稳定的配对结果[^4]。 Gale-Shapley 算法的核心在于迭代过程——未成功配对的一方会不断向另一方发出请求直到所有人都完成配对为止。虽然此方法不总是最大化整体满意度,但它能有效避免不稳定状态的发生。 ```python def gale_shapley(preferences_men, preferences_women): n = len(preferences_men) free_men = set(range(n)) engaged = {} proposals = {m: 0 for m in range(n)} while free_men: man = next(iter(free_men)) woman_rank = proposals[man] woman = preferences_men[man][woman_rank] current_partner = engaged.get(woman, None) if not current_partner or \ preferences_women[woman].index(man) < preferences_women[woman].index(current_partner): engaged[woman] = man if current_partner is not None: free_men.add(current_partner) free_men.remove(man) proposals[man] += 1 return [(w, e[w]) for w, e in zip(engaged.keys(), engaged.values())] preferences_men = [ [0, 1], [1, 0] ] preferences_women = [ [1, 0], [0, 1] ] result = gale_shapley(preferences_men, preferences_women) print(result) ``` 这段代码展示了如何应用 Gale-Shapley 来处理两个群体间的相互选择问题。尽管这里只涉及两个人群的小规模例子,但实际操作中它可以扩展到更大的数据集上运行[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值