二分图的最大匹配-解决匹配问题

题目描述

题目描述
若两个正整数的和为素数,则这两个正整数称之为“素数伴侣”,如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个整数

输出描述:

 

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

示例1

输入

复制

4
2 5 6 13

输出

复制

2

 

 

 

 

【问题分析】:

从给定的数中取两个数,这两个数的和满足素数,完成一个匹配,容易知道,其中一个数为偶数,另一个数为奇数

(否则和为偶数,必不可能为素数)

因此将原先的数分成两部分,其中一部分为奇数,另一部分为偶数。

原问题变为:完成从《奇数,偶数》的勾连(勾连的条件为和为素数),使得最终匹配的总数最多

 

该问题即为二分图的最大匹配,二分图中包含两部分点,每一个点只能与另一部分点中的部分相连

连接两部分中若干点,即两两配对,求最大匹配数。

 

匈牙利算法:

 

将点分成上下两部分,奇数arr1[]  和   偶数arr2[]

每一轮考察上面的一个点U   遍历下方的点,对于其中一个点V  若(u,v)能够匹配(如组合成素数) 并且 本轮没有访问到该点

  • 那么判断 v点是否已经匹配过,如果没有那么直接匹配
  • 如果v点之前匹配过(match[v]!=-1) 那么v点试图抛弃match[v] (即match[v]点能够找到其他匹配的点) 若成功,那么直接抛弃

 

实现逻辑:


bool find(int u)  //奇数第u个寻找偶数匹配  组配成素数组合
{
    for (int v = 0; v < N; v++)
    {
        //u v可以组配成素数组合  并且本轮寻找增广路  v还没被讨论(每一轮都要初始化)
        if (map[u][v] && !visit[v])
        {
            visit[v] = true;
            //若v点还没有匹配(=-1)  或者能够将匹配任务推卸给 v之前的匹配对象
            if (match[v] == -1 || find(match[v]))
            {

              
                match[v] = u;  //构建新的匹配
                return true;
            }


        }
    }
  
    return false;

}

关键易错点:

visit数组的理解,其在每一轮都需要初始化为false,  一轮可以理解为一个奇数进行匹配,

另外一个实现是素数的判断:

任何一个整数可以划分为6i 6i+1 6i+2 6i+3 6i+4 6i+5

首先6i 6i+2 6i+3 6i+4的类别的数一定不是素数,因为可以被2 或者 3整除

因此只有满足一个数num  其 num%6==1 或  num%6==5才可能为素数

接下来从i=2 循环到 i=sqrt(num)  判断是不是i都不能被num整除

进一步优化:

其中一些i不需要判断,这些i满足 6k 6k+2 6k+3 6k+4  

反证法,如果这些i能被num整除,那么num一定也满足 6k 6k+2 6k+3 6k+4 而这与num一定为6k+1或6k+5不符合

bool isPrime(int num)
{

    int data = num;
    int loopNum = sqrt(num);
    for (int i = 2; i <= loopNum; i++)
    {
        if (data % i == 0)
        {
            return 0;
        }
    }
    return 1;

    if (num < 4)
        return num > 1;


    //任何的数可以划分为  6i 6i+1 6i+2 6i+3 6i+4 6i+5   
    //可以看到只能是6i+1 6i+5可能是素数
    if (num % 6 != 1 && num % 6 != 5)
        return false;

    //接下来验证 从i=2到i=sqrt(Num)是否能被Num整除
    //可以排除 被除数为   6i+2 6i+3 6i+4 6i   反证:如果num能被6i整除  那么num是6的倍数  与num=6i+1或6i+5不符合


    for (int i = 5; i < sqrt(num); i += 6)
    {
        if (num % (i) == 0 || num % (i + 2) == 0)
            return false;
    }

    return true;

}

 

如何统计最大匹配数,

即针对每一个奇数,调用find() 若返回true  那么count++

而每一轮结束,初始化visit={false}

 

 

完整代码:

#include
#include
#include
#include
#include
using namespace std;
int M, N;
bool visit[100] = { false };//标记一轮中待匹配元素是否被访问数组
int arr1[100] = { -1 };//奇数
int arr2[100] = { -1 };//偶数
bool map[100][100] = { false };//map[i][j]表示奇数的第i个与偶数的第j个组合成素数
int match[100];//match[j]表示第j个偶数的匹配的奇数


bool isPrime(int num)
{

    int data = num;
    int loopNum = sqrt(num);
    for (int i = 2; i <= loopNum; i++)
    {
        if (data % i == 0)
        {
            return 0;
        }
    }
    return 1;

    if (num < 4)
        return num > 1;


    //任何的数可以划分为  6i 6i+1 6i+2 6i+3 6i+4 6i+5   
    //可以看到只能是6i+1 6i+5可能是素数
    if (num % 6 != 1 && num % 6 != 5)
        return false;

    //接下来验证 从i=2到i=sqrt(Num)是否能被Num整除
    //可以排除 被除数为   6i+2 6i+3 6i+4 6i   反证:如果num能被6i整除  那么num是6的倍数  与num=6i+1或6i+5不符合


    for (int i = 5; i < sqrt(num); i += 6)
    {
        if (num % (i) == 0 || num % (i + 2) == 0)
            return false;
    }

    return true;





}



bool find(int u)  //奇数第u个寻找偶数匹配  组配成素数组合
{
    for (int v = 0; v < N; v++)
    {
        //u v可以组配成素数组合  并且本轮寻找增广路  v还没被讨论(每一轮都要初始化)
        if (map[u][v] && !visit[v])
        {

           
            visit[v] = true;
            //若v点还没有匹配(=-1)  或者能够将匹配任务推卸给 v之前的匹配对象
            if (match[v] == -1 || find(match[v]))
            {

               
                match[v] = u;  //构建新的匹配
                return true;
            }


        }
    }
    return false;

}





int main()
{
    int num;
    cin >> num;



    for (int i = 0; i < num; i++)
    {
        int tmp;
        cin >> tmp;
        if ((tmp & 1) == 1)  //奇数
        {
            arr1[M++] = tmp;
        }
        else  //偶数
        {
            arr2[N++] = tmp;
        }

    }

    for (int i = 0; i < M; i++)
        for (int j = 0; j < N; j++)
        {
            if (isPrime(arr1[i] + arr2[j]))  //和为素数
                map[i][j] = true;//标记为能匹配
          
        }

    for (int i = 0; i < N; i++)
        match[i] = -1;


    int count = 0;
    for (int i = 0; i < M; i++)
    {
        //注意每一轮研究 奇数的第i个能否完成匹配,
        //每一轮研究前  都需要将偶数的每个数初始化为 没有讨论visit=false;
        for (int j = 0; j < N; j++)
            visit[j] = false;
        if (find(i))
            count++;
    }
    cout << count << endl;
}

 

 

 

 

你可能感兴趣的:(算法,Leetcode)