zoukankan      html  css  js  c++  java
  • 二分图最大匹配(匈牙利算法):素数伴侣

    题目描述

      若两个正整数的和为素数,则这两个正整数称之为“素数伴侣”,如2和5、6和13,它们能应用于通信加密。现在密码学会请你设计一个程序,从已有的N(N为偶数)个正整数中挑选出若干对组成“素数伴侣”,挑选方案多种多样,例如有4个正整数:2,5,6,13,如果将5和6分为一组中只能得到一组“素数伴侣”,而将2和5、6和13编组将得到两组“素数伴侣”,能组成“素数伴侣”最多的方案称为“最佳方案”,当然密码学会希望你寻找出“最佳方案”。

    输入

      有一个正偶数N(N≤100),表示待挑选的自然数的个数。后面给出具体的数字,范围为[2,30000]。

    输出

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

    输入描述

      1、 输入一个正偶数n
      2 、输入n个整数

    输出描述

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

    输入

    4
    2 5 6 13

    输出

    2

    思路

      考虑使用图论的方法来给这个题建模。100个数看成100个点,如果这两个数加起来的和是素数,给这两个数中间连一条边。之后,我们要选择尽可能多的边,要求这些边不共享端点。每个数的取值范围是[2,30000]。素数除了2是偶数,其他是奇数——而现在不可能出现2了,所以我们只考虑奇数的素数。2个数的和是奇数,只有奇数+偶数。所以,我们把这些数分成2堆——奇数和偶数,得到二分图。然后在他们中间,和是素数的,连上一条边,然后做匹配。对二分图的最大匹配,有一个简单很多的算法,匈牙利算法

    相关概念

    二分图
      二分图其实就是在一个图中所有的点可以分为两组,同一组中没有边,所有的边都跨越了两个组。准确的说:把一个图的顶点划分为两个不相交的集合 U 和 V ,且使得每一条边都分别连接 U 、V 中的顶点,如果存在这样的划分,则称此图为二分图。
      此外二分图还有一个等价定义是:不含有「含奇数条边的环」的图。
    匹配
      二分图匹配就是边的集其中任意两条边没有公共顶点。我们定义有:
           匹配边、匹配点、非匹配边、非匹配点。
      如图:若1—2相连,5—4相连,7—6相连。则显然1—2边、5—4边、7—6边为匹配边,1、2、4、5、6、7为匹配点。剩下的为非匹配点和非匹配边。

    最大匹配
      一个图的匹配中所含边数最多的匹配即为此图最大匹配。像上图最大匹配即为:


      显然最大匹配可能不只有一种。
    完美匹配
      如果一个图的某个匹配中,所有的顶点都是匹配点,那么它就是一个完美匹配。上图都是完美匹配。显然,完美匹配一定是最大匹配(完美匹配的任何一个点都已经匹配,添加一条新的匹配边一定会与已有的匹配边冲突)。但并非每个图都存在完美匹配。
    交替路径
      从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边……形成的路径叫交替路径。如图2—>1—>8—>7即为一条交替路径。

    增广路径
      从一个未匹配点出发,走交替路径,如果到达另一个未匹配点,则这条交替路径称为增广路径(agumenting path)。如图1—>2—>3—>6—>7—>8即为一条增广路径。

    增广路径性质
      由增广路的定义可以推出下述三个结论(设当前路径为P,当前匹配为M):
      1、P的路径长度必定为奇数,第一条边和最后一条边都不属于M,因为两个端点分属两个集合,且未匹配。
      2、P经过取反操作可以得到一个更大的匹配M’。
      3、M为G的最大匹配当且仅当不存在相对于M的增广路径。

    匈牙利算法:用增广路径求最大匹配
      1、置M为空 。
      2、找出一条增广路径P,通过取反操作获得更大的匹配M’代替M 3、重复2操作直到找不出增广路径为止 。

    寻找增广路径算法
      我们采用DFS的办法找一条增广路径,从X部一个未匹配的顶点u开始,找一个未访问的邻接点v(v一定是Y部顶点)。对于v,分两种情况:
      1、如果v未匹配,则已经找到一条增广路。
      2、如果v已经匹配,则取出v的匹配顶点w(w一定是X部顶点),边(w,v)目前是匹配的,根据“取反”的想法,要将(w,v)改为未匹配,(u,v)设为匹配,能实现这一点的条件是看从w为起点能否新找到一条增广路径P’。如果行,则u-v-P’就是一条以u为起点的增广路径。
    代码如下

     1 #include<iostream>
     2 #include<cstring>
     3 #include<vector>
     4 using namespace std;
     5 const int N = 100, M = 60001;
     6 vector<int> g[N]; //二分图
     7 int pre[N], nums[N];  //偶数对应的当前奇数匹配点,输入的数
     8 bool isPrime[M], flag[N]; //素数对应表,可匹配点是否已匹配
     9 //欧拉筛表建立素数对应表
    10 void primeTab() {
    11     int prime[M], cnt = 0;
    12     prime[cnt++] = 2;
    13     memset(isPrime, true, sizeof(isPrime));
    14     isPrime[0] = isPrime[1] = false;
    15     for (int i = 4; i < M; i += 2)
    16         isPrime[i] = false;
    17     for (int i = 3; i < M; i += 2) {
    18         if (isPrime[i])
    19             prime[cnt++] = i;
    20         for (int j = 0; j < cnt && i * prime[j] < M; ++j) {
    21             isPrime[i * prime[j]] = false;
    22             if (i % prime[j] == 0)
    23                 break;
    24         }
    25     }
    26 }
    27 //深度优先搜索增广路径,从而找到最大匹配
    28 bool dfs(int n) {
    29     for (int i = 0; i < g[n].size(); ++i) {
    30         int x = g[n][i];
    31         if (flag[x])   //若nums[x]是nums[n]已经匹配的点,现在需要将其腾出
    32             continue;
    33         flag[x] = true; //nums[x]成为nums[n]的匹配点
    34         //nums[x]未被匹配||nums[x]已被匹配但可以让其匹配点寻找新的匹配点从而腾出nums[x]
    35         if (pre[x] == -1 || dfs(pre[x])) {  
    36             pre[x] = n;   //给nums[n]匹配nums[x]
    37             return true;
    38         }
    39     }
    40     return false;
    41 }
    42 int main() {
    43     int n;
    44     primeTab(); //建立素数对应表
    45     /*
    46     pre默认赋为-1而非0,这是因为pre存储的是奇数在nums中的下标。
    47     若pre默认赋0则pre[i]=0有两种意义:
    48     (1)pre[i]映射到无;(2)pre[i]映射到nums[0]。
    49     互相矛盾,故赋-1。
    50     */
    51     memset(pre, -1, sizeof(pre)); 
    52     while (cin >> n) {
    53         for (int i = 0; i < n; ++i)
    54             cin >> nums[i];
    55         for (int i = 0; i < n - 1; ++i)
    56             for (int j = i + 1; j < n; ++j)
    57                 if (isPrime[nums[i] + nums[j]])
    58                     nums[i] & 1 ? g[i].push_back(j) : g[j].push_back(i);  //链表法建立二分图,奇数链接偶数可匹配点
    59         int ans = 0;
    60         for (int j = 0; j < n; ++j) {
    61             memset(flag, false, sizeof(flag));
    62             if (dfs(j))
    63                 ++ans;
    64         }
    65         cout << ans << endl;
    66         for (int i = 0; i < n; ++i) {  //清空二分图映射关系
    67             g[i].clear();
    68         }
    69         memset(pre, -1, sizeof(pre));   //清空偶数对应的当前奇数匹配点
    70     }
    71 }
  • 相关阅读:
    BZOJ 2654: tree
    洛谷P1972[SDOI2009]HH的项链
    洛谷 P3833 [SHOI2012]魔法树
    P2167 [SDOI2009]Bill的挑战
    洛谷 P2145 [JSOI2007]祖码
    洛谷 P4170 [CQOI2007]涂色
    P2024 [NOI2001]食物链
    USACO 2012 December ZQUOJ 24122 Scrambled Letters(二分)
    USACO 2012 December ZQUOJ 24128 Wifi Setup(动态dp)
    2013长春网赛1009 hdu 4767 Bell(矩阵快速幂+中国剩余定理)
  • 原文地址:https://www.cnblogs.com/xiehuazhen/p/12574797.html
Copyright © 2011-2022 走看看