首页 > 试题广场 >

素数伴侣

[编程题]素数伴侣
  • 热度指数:177985 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32M,其他语言64M
  • 算法知识视频讲解

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

输入:

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

输出:

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


数据范围: ,输入的数据大小满足

输入描述:

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



输出描述:

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

示例1

输入

4
2 5 6 13

输出

2
示例2

输入

2
3 6

输出

0
推荐
OK,我们把这个话题终结一下。

所有想着动态规划的,你们放弃吧,不是这么玩的。

考虑使用图论的方法来给这个题建模。
100个数看成100个点,如果这两个数加起来的和是素数,给这两个数中间连一条边。
之后,我们要选择尽可能多的边,要求这些边不共享端点。
——这个东西呢,叫做一般无向图的最大匹配。

但是,一般无向图的最大匹配,这个算法的难度,有点,大……
这个问题,合适的算法叫,带花树开花算法。
现场推完这么多,写一个正确的,有点不科学……

我们考虑再分析一下这个题
注意到,每个数的取值范围是[2,30000]
素数,除了2是偶数,其他是奇数——而现在不可能出现2了,所以我们只考虑奇数的素数
2个数的和是奇数,有什么情况呢?
只有奇数+偶数
所以,我们把这些数分成2堆——奇数和偶数,然后在他们中间,和是素数的,连上一条边,然后做匹配。
——肯定有人会说,你这个和前面的建图有什么本质不同的地方吗?
——是没有,但是我们说明了得到的图一定是二分图,这是有意义的。
因为对二分图的最大匹配,有一个简单很多的算法,匈牙利算法。

我们先说明一下,什么是二分图。
二分图就是,你可以把图上的点分成2堆,每堆之内的点不会有边,2堆之间,才可能连边。换句话说,一条边,必定连2个来自不同堆的点。
现在,对每条边,一定连一个奇数,一个偶数,点能按奇数和偶数分成两部分,刚好就是二分图嘛!

有关二分图匹配的匈牙利算法,具体请自行搜索,这里扯一下我个人对这个算法的理解。

外层,暴力考虑左边每个点
对枚举的每个左边的点,要找右边一个点来匹配。
那就是对左边的点,我们看他连出去的边,或者说,能连到的右边的点
有2种情况:
1、右边的点没人匹配——我直接贪心匹配上去
2、右边的点有人匹配——考虑把目前与这个右边的点 x 匹配的左边的点 pre[x],重新匹配一个其他的点,如果成功了,那pre[x]原来匹配的点x就释放开了,我可以直接占领上去。
最后统计匹配成功多少个左边的点就行了。

匈牙利算法真的还是很短的,比你写一个红黑树轻松多了:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <math.h>
#include <algorithm>
#include <vector>
using namespace std;

vector<int> G[105];

bool flag[105];
int pre[105];

bool dfs(int k){
    int x;
    for(int i=0;i<G[k].size();i++){
        x=G[k][i];
        if (flag[x]) continue;
        flag[x]=true;
        if((pre[x]==0)||dfs(pre[x])){
            pre[x]=k;
            return true;
        }
    }
    return false;
}

bool isprime[80000];
int nums[105];

int main(){
    memset(isprime,1,sizeof(isprime));
    isprime[0]=isprime[1]=false;
    for(int i=4;i<80000;i+=2)isprime[i]=false;
    for(int i=3;i*i<80000;i+=2)
        if(isprime[i]){
            for(int j=i*i;j<80000;j+=2*i) isprime[j]=false;
        }
    int n;
    while(~scanf("%d",&n)){
        for(int i=1;i<=n;i++){
            scanf("%d",&nums[i]);
        }
        for(int i=1;i<=n;++i){
            for(int j=i+1;j<=n;++j){
                if(isprime[nums[i]+nums[j]]){
                    (nums[i]&1)?G[i].push_back(j):G[j].push_back(i);
                }
            }
        }

        memset(pre,0,sizeof(pre));
        int ans=0;
        for(int i=1;i<=n;i++){
            memset(flag,false,sizeof(flag));
            if (dfs(i)) ans++;
        }
        printf("%d\n",ans);
        
        for(int i=1;i<=n;++i){
            G[i].clear();
        }
    }
    return 0;
}

(当然,你用网络流做二分图最大匹配,没人拦你。)

最后,给好学的人:
一般图匹配(牛刀)来做这个题(杀鸡)的代码:
带花树开花的模板 CopyRight Claris(http://www.lydsy.com/JudgeOnline/userinfo.php?user=Claris
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int N=510;
int n,m,x,y;vector<int>g[N];
namespace Blossom{
    int mate[N],n,ret,nxt[N],f[N],mark[N],vis[N];queue<int>Q;
    int F(int x){return x==f[x]?x:f[x]=F(f[x]);}
    void merge(int a, int b){f[F(a)]=F(b);}
    int lca(int x, int y){
        static int t=0;t++;
        for(;;swap(x,y)) if(~x){
            if(vis[x=F(x)]==t) return x;vis[x]=t;
            x=mate[x]!=-1?nxt[mate[x]]:-1;
        }
    }
    void group(int a, int p){
        for(int b,c;a!=p;merge(a,b),merge(b,c),a=c){
            b=mate[a],c=nxt[b];
            if(F(c)!=p)nxt[c]=b;
            if(mark[b]==2)mark[b]=1,Q.push(b);
            if(mark[c]==2)mark[c]=1,Q.push(c);
        }
    }
    void aug(int s, const vector<int>G[]){
        for(int i=0;i<n;++i)nxt[i]=vis[i]=-1,f[i]=i,mark[i]=0;
        while(!Q.empty())Q.pop();Q.push(s);mark[s]=1;
        while(mate[s]==-1&&!Q.empty()){
        int x=Q.front();Q.pop();
        for(int i=0,y;i<(int)G[x].size();++i){
                if((y=G[x][i])!=mate[x]&&F(x)!=F(y)&&mark[y]!=2){
                    if(mark[y]==1){
                        int p=lca(x,y);
                        if(F(x)!=p)nxt[x]=y;
                        if(F(y)!=p)nxt[y]=x;
                        group(x,p),group(y,p);
                    }else if(mate[y]==-1){
                        nxt[y]=x;
                        for(int j=y,k,l;~j;j=l)k=nxt[j],l=mate[k],mate[j]=k,mate[k]=j;
                        break;
                    }else nxt[y]=x,Q.push(mate[y]),mark[mate[y]]=1,mark[y]=2;
                }
            }
        }
    }
    int solve(int _n, const vector<int>G[]){
        n=_n;memset(mate, -1, sizeof mate);
        for(int i=0;i<n;++i) if(mate[i]==-1)aug(i,G);
        for(int i=ret=0;i<n;++i)ret+=mate[i]>i;
        printf("%d\n",ret);
        //for(int i=0;i<n;i++)printf("%d %d\n",i+1,mate[i]+1);
        return ret;
    }
}
bool isprime[80000];
int nums[105];
int main(){
    memset(isprime,1,sizeof(isprime));
    isprime[0]=isprime[1]=false;
    for(int i=4;i<80000;i+=2)isprime[i]=false;
    for(int i=3;i*i<80000;i+=2)
        if(isprime[i]){
            for(int j=i*i;j<80000;j+=2*i) isprime[j]=false;
        }
    while(~scanf("%d",&n)){
        for(int i=0;i<n;i++){
            scanf("%d",&nums[i]);
        }
        for(int i=0;i<n;++i){
            for(int j=i+1;j<n;++j){
                if(isprime[nums[i]+nums[j]]){
                    g[i].push_back(j);
                    g[j].push_back(i);
                }
            }
        }
        Blossom::solve(n,g);
        for(int i=0;i<n;++i){
            g[i].clear();
        }
    }
}

最后吐槽:华为这题出得都快和微软一个尿性了,二分图匹配和DAG上求支配者树,这个都不怎么好想出来的,硬生生想出来的,这个值得膜一膜,但是做出这个题的人,更多情况是,学过这个东西吧?
(你看这题的讨论区,多少人跳进了dp的大坑爬不出来?)
于是这是笔试题/面试题从一个极端走向另一个极端(从语言和库知多少,走向算法知识知多少
编辑于 2016-09-10 13:03:39 回复(47)
import java.util.ArrayList;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (sc.hasNext()) {
            int n = sc.nextInt();
            ArrayList<Integer> odds = new ArrayList<>();
            ArrayList<Integer> evens = new ArrayList<>();
            for (int i = 0; i < n; i++) {
                int num = sc.nextInt();
                if (num % 2 == 0) {
                    evens.add(num);
                } else {
                    odds.add(num);
                }
            }
            int[] matcheven = new int[evens.size()];
            int count = 0;
            for (Integer odd : odds) {
                if (find(odd, matcheven, evens, new boolean[evens.size()])) {
                    count++;
                }
            }
            System.out.println(count);
        }
    }

    private static boolean find(int x, int[] matcheven, ArrayList<Integer> evens,
                                boolean[] v) {
        for (int i = 0; i < evens.size(); i++) {
            int even = evens.get(i);
            if (isPrime(x + even) && !v[i]) {
                v[i] = true;
                if (matcheven[i] == 0 || find(matcheven[i], matcheven, evens, v)) {
                    matcheven[i] = x;
                    return true;
                }
            }
        }
        return false;
    }

    private static boolean isPrime(int x) {
        if (x <= 1 || (x > 2 && x % 2 == 0)) {
            return false;
        }
        for (int i = 3; i <= Math.sqrt(x); i += 2) {
            if (x % i == 0) {
                return false;
            }
        }
        return true;
    }
}


  1. 首先,我们读入所有的数据,将所有的奇数和偶数分别存入两个列表。
    		
    java
    int n = sc.nextInt(); ArrayList<Integer> odds = new ArrayList<>(); ArrayList<Integer> evens = new ArrayList<>(); for(int i = 0; i < n; i++){ int num = sc.nextInt(); if (num % 2 == 0) { evens.add(num); } else { odds.add(num); } }
  2. 初始化匹配数组 matcheven,它的下标对应偶数列表中的数字,值对应这个偶数的伴侣(如果有的话)。

    		
    java
    int[] matcheven = new int[evens.size()];
  3. 对于每一个奇数,调用 find 函数尝试找到一个与它可以形成素数伴侣的偶数。如果找到了,那么匹配的数量 count 就加一。

    		
    java
    int count = 0; for (Integer odd : odds) { if (find(odd, matcheven, evens, new boolean[evens.size()])) { count++; } }
  4. 在 find 函数中,我们遍历所有的偶数,如果找到一个偶数 even,它和当前的奇数 x 之和是素数,并且还没有被访问过,那么我们就尝试匹配它。

    		
    java
    for (int i = 0; i < evens.size(); i++) { int even = evens.get(i); if (isPrime(x + even) && !v[i]) { v[i] = true; if (matcheven[i] == 0 || find(matcheven[i], matcheven, evens, v)) { matcheven[i] = x; return true; } } }

    这里的 v[i] 是用来标记偶数 even 是否被访问过的。

    如果 even 还没有伴侣,那么我们就直接将它与当前的奇数 x 匹配。

    如果 even 已经有伴侣,那么我们就尝试为它的当前伴侣找到一个新的伴侣。如果找到了,那么我们就将 even 的伴侣更换为当前的奇数 x。

  5. 最后,我们打印出匹配的数量 count,这就是最大的素数伴侣对数。

    		
    java
    System.out.println(count);
发表于 2024-09-21 11:38:34 回复(0)
抢伴侣算法:如果对方单身,并且能跟你匹配,就匹配成功了;如果对方有伴侣,你可以尝试去抢过来,但是得保证被抢的人还能找到接盘的就行
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class 我的素数伴侣 {

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextInt()) { // 注意 while 处理多个 case
            int n = in.nextInt();
            // 素数:两个数组合在一起还是素数的话,除了两个1之外,就只剩下一奇数一偶数了
            List<Integer> odds = new ArrayList<>();
            List<Integer> evens = new ArrayList<>();
            for (int i = 0; i < n; i++) {
                int temp = in.nextInt();
                // 奇数,后边先打印一下,确认这里没问题
                if ((temp & 1) == 1) {
                    odds.add(temp);
                } else {
                    evens.add(temp);
                }
            }
            // 偶数的匹配情况,下边数组的索引代表第几个偶数被谁匹配了,如果对应的位置的值是 0 ,则没有匹配
            int[] evenMatched = new int[evens.size()];
            int result = 0;
            for (Integer odd : odds) {
                // 在接下来的一整个寻找过程中,要认为所有的偶数我都可以去挑选,因为【匈牙利算法的思想就是:先到先得,能让则让】
                // 所以其实每次都可以去抢别人的伴侣,如果被抢的人还能找到其他伴侣的话,则抢成功
                boolean[] used = new boolean[evens.size()];
                if (canMatch(used, odd, evens, evenMatched)) {
                    result++;
                }
            }
            System.out.println(result);
        }
    }

    private static boolean canMatch(boolean[] used, Integer odd, List<Integer> evens, int[] evenMatched) {
        for (int i = 0; i < evens.size(); i++) {
            Integer even = evens.get(i);
            // 如果当前的偶数与当前的奇数匹配,并且当前的偶数没被用掉
            if (isPrime(odd+ even) && !used[i]) {
                // 被用掉了,不管是被强抢了还是直接匹配成功了,当前的偶数都被用掉了
                used[i] = true;

                // evenMatched[i]==0:当前的偶数没有伴侣
                // canMatch(used, evenMatched[i], evens, evenMatched):
                //      当前的偶数有伴侣,但是当前的偶数的伴侣,还能找到其他的伴侣,一直递归下去
                //      大体意思就是:你要么找一个单身的,要么如果去抢别人的伴侣的话,得确保被抢的单下来的一方还能找到另一个伴侣。可以递归去抢,但是得保证递归到最后的那个人也还是能再找到一个伴侣
                if (evenMatched[i]==0 || canMatch(used, evenMatched[i], evens, evenMatched)) {
                    evenMatched[i] = odd;
                    return true;
                }
            }
        }
        return false;
    }

    private static boolean isPrime(int num) {
        if (num==1) {
            return false;
        }
        if (num==2) {
            return true;
        }
        for (int i=2;i<=Math.sqrt(num);i++) {
            if (num%i==0) {
                return false;
            }
        }
        return true;
    }

}


编辑于 2024-04-16 23:24:18 回复(0)
为啥提交显示用例只通过了50%啊,我没看出哪里有问题啊
编辑于 2024-02-03 21:40:00 回复(0)
学习到了匈牙利算法,此算法是获取二分图中最大匹配数的。此题的很关键的一点是是二分图的获取,素数=基数+偶数.将原始数据通过基偶构建二分图,之后使用匈牙利算法获取最大匹配数;关键词"最大匹配数"、"二分图";
对于java实现匈牙利算法,其中核心的是1:匹配条件(此题是二分图中两者之和成为质数)2:增广路(此题是int[] ouSeted),用于存储成功匹配二分图元素(一般是左边元素)3:标记数组(boolean[] onPath),每轮查询会重置元素,对当前已配对的偶数伴侣数组索引位置标记
import java.util.*;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextInt()) { // 注意 while 处理多个 case
            int num = in.nextInt();
            ArrayList<Integer> jiArr =  new ArrayList();
            ArrayList<Integer> ouArr =  new ArrayList();
            for (int i = 0; i < num; i++ ) {
                int a = in.nextInt();
                if (a % 2 == 0) {
                    ouArr.add(a);
                } else {
                    jiArr.add(a);
                }
            }
            int count = 0;
            int[] ouSeted = new int[ouArr.size()];//存放偶数伴侣
            for (int i = 0; i < jiArr.size(); i++ ) {
                boolean[] onPath = new boolean[ouArr.size()]; //对当前已配对的偶数伴侣数组索引位置标记

                if (search(jiArr.get(i), onPath, ouArr, ouSeted)) {
                    count++;
                }
            }
            System.out.println(count);
        }
    }
    //对基数进行查找
    private static boolean search(int jisu, boolean[] onPath,
                                  ArrayList<Integer> ouArr, int[] ouSeted ) {
        for (int j = 0; j < ouArr.size(); j++ ) {
            int b = jisu + ouArr.get(j);
            if (!onPath[j] && recursion(b)) {
                onPath[j] = true;
                if (ouSeted[j] == 0 || search(ouSeted[j], onPath, ouArr, ouSeted)) {
                    ouSeted[j] = jisu;
                    return true;
                }
            }
        }
        return false;
    }

    //判断一个数是否为素数
    private static boolean recursion(int inNum) {
        for (int i = 2; i <= Math.sqrt(inNum); i++) {
            if (inNum % i == 0) {
                return false;
            }
        }
        return true;
    }
}



编辑于 2023-12-25 15:15:22 回复(2)
 public static void main(String[] args) {
        //标准输入
        Scanner sc = new Scanner(System.in);
        while (sc.hasNext()) {
            //输入正偶数
            int n = sc.nextInt();
            //用于记录输入的n个整数
            int[] arr = new int[n];
            //用于存储所有的奇数
            ArrayList<Integer> odds = new ArrayList<>();
            //用于存储所有的偶数
            ArrayList<Integer> evens = new ArrayList<>();
            for (int i = 0; i < n; i++) {
                arr[i] = sc.nextInt();
                //将奇数添加到odds
                if (arr[i] % 2 == 1) {
                    odds.add(arr[i]);
                }
                //将偶数添加到evens
                if (arr[i] % 2 == 0) {
                    evens.add(arr[i]);
                }
            }
            //下标对应已经匹配的偶数的下标,值对应这个偶数的伴侣
            //注意1:参考str = "4 6 8 5 11 13";  5+6=11,11+6=17,互相争夺6
            //注意2:matcheven是全局的匹配的总数组,每个位置代表偶数的位置配对的奇数数值;
            //注意3:v数值代表每个奇数抢夺偶数时,奇数是否使用过的标志,每次奇数都会重置该数组,递归时使用它判断是否被用过;
            //注意4:当前偶数位置的配对奇数已经存在值matcheven[i]时,就要判断奇数值能否在非当前偶数之外找到素数伴侣,如果该奇数能找到新素数伴侣,则把该伴侣让给后来的奇数,依次类推;
            //注意5:该方法使用了两个利用偶数位置的数组,一个代表该偶数匹配的素数伴侣数组,另一个代表每次奇数抢夺偶数时记录偶数是否被占用的布尔数组
            int[] matcheven = new int[evens.size()];
            //记录伴侣的对数
            int count = 0;
            for (int j = 0; j < odds.size(); j++) {
                //用于标记对应的偶数是否查找过
                boolean[] v = new boolean[evens.size()];
                //如果匹配上,则计数加1
                if (find(odds.get(j), matcheven, evens, v)) {
                    count++;
                }
            }
            System.out.println(count);
        }
    }

    //判断奇数x能否找到伴侣
    private static boolean find(int x, int[] matcheven, ArrayList<Integer> evens,
                                boolean[] v) {
        for (int i = 0; i < evens.size(); i++) {
            //该位置偶数没被访问过,并且能与x组成素数伴侣
            if (isPrime(x + evens.get(i)) && v[i] == false) {
                v[i] = true;
                /*如果i位置偶数还没有伴侣,则与x组成伴侣,如果已经有伴侣,并且这个伴侣能重新找到新伴侣,
                则把原来伴侣让给别人,自己与x组成伴侣*/
                if (matcheven[i] == 0 || find(matcheven[i], matcheven, evens, v)) {
                    matcheven[i] = x;
                    return true;
                }
            }
        }
        return false;
    }
    //判断x是否是素数
    private static boolean isPrime(int x) {
        if (x == 1) return false;
        //如果能被2到根号x整除,则一定不是素数
        for (int i = 2; i <= Math.sqrt(x)+0.5; i++) {
            if (x % i == 0) {
                return false;
            }
        }
        return true;
    }
发表于 2023-04-25 20:57:05 回复(0)
练习一下,写个遍历所有组合可能的,通过率不高
import java.util.ArrayList;
import java.util.Scanner;

public class Main {

    private static int max = 0;


    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();

        ArrayList<Integer> oddList = new ArrayList<>();
        ArrayList<Integer> evenList = new ArrayList<>();


        for (int i = 0; i < n; i++) {
            int temp = scanner.nextInt();
            if (temp % 2 == 0) {
                evenList.add(temp);
            } else {
                oddList.add(temp);
            }
        }

        int[] odd = new int[oddList.size()];
        int[] even = new int[evenList.size()];

        for (int i = 0; i < oddList.size(); i++) {
            odd[i] = oddList.get(i);
        }

        for (int i = 0; i < evenList.size(); i++) {
            even[i] = evenList.get(i);
        }


        dp(odd, even, 0);
        System.out.println(max);
    }


    public static boolean isPrime(int item) {
        for (int i = 2; i <= Math.sqrt(item); i++) {
            if (item % i == 0) {
                return false;
            }
        }
        return true;
    }


    public static void dp(int[] odd, int[] even, int counter) {

        for (int i = 0; i < odd.length ; i++) {

            if (odd[i] != 0) {
                int a = odd[i];
                odd[i] = 0;

                for (int j = 0; j < even.length; j++) {

                    if (even[j] != 0) {
                        int b = even[j];
                        even[j] = 0;
                        
                        if (isPrime(a + b)) {
                            dp(odd, even, counter + 1);
                        }

                        even[j] = b;
                    }
                }

                odd[i] = a;
            }

        }

        max = Math.max(max, counter);
    }

}


发表于 2023-03-14 13:50:00 回复(0)
典型的匈牙利算法:先到先得,能让就让,还是不错的
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 获取输入的正整数个数
        int n = in.nextInt();
        // 定义一个集合,用于存放这n个正整数中奇数
        List<Integer> jishu = new ArrayList<>();
        // 定义一个集合,用于存放这n个正整数中的偶数
        List<Integer> oushu = new ArrayList<>();
        // 读取输入的正整数
        for (int i = 0; i < n; i++) {
            int m = in.nextInt();
            if (m % 2 == 0) {
                //偶数
                oushu.add(m);
            } else {
                // 奇数
                jishu.add(m);
            }
        }
        // 查找素数伴侣,返回查找到的最大对数
        int max = findResult(jishu, oushu);
        // 输出最大对数
        System.out.println(max);
    }
    /**
     * 查找n个正整数中存在素数伴侣的最大对数.
     */
    private static int findResult(List<Integer> jishu, List<Integer> oushu) {
        // 定义找到的最大对数
        int max = 0;
        // 定义一个数组,用于存放偶数的伴侣,这个数组里面存放的是奇数列表中的值,默认0填充
        int[] oushuSoul = new int[oushu.size()];
        // 对奇数进行遍历,判断该奇数能否找到偶数伴侣
        for (int i = 0; i < jishu.size(); i++) {
            // 定义一个数组,用于存放偶数列表中的偶数是否被该奇数配对过
            boolean[] isPair = new boolean[oushu.size()];
            // 如果奇数和偶数能够形成最优配对,则max加1
            if (isPairSuccess(jishu.get(i), oushu, oushuSoul, isPair)) {
                max++;
            }
        }
        return max;
    }
    /**
     * 判断奇数 jishu,是否能够在oushu列表中形成最优配对
     */
    private static boolean isPairSuccess(int jishu, List<Integer> oushu,
                                         int[] oushuSoul,
                                         boolean[] isPair) {
        // 将这个奇数依次与偶数进行配对,看是否存在最优配对
        for (int i = 0; i < oushu.size(); i++) {
            // 如果两则相加为素数,切该位置还没有配对过,则可组成素数伴侣,但最优素数伴侣得继续进行条件判断
            if (isPrimeNumber(jishu + oushu.get(i)) && isPair[i] == false) {
                // 将该偶数设置为已配对过
                isPair[i] = true;
                // 匈牙利算法,先到先得,能让就让;如果该位置的偶数还没有奇数伴侣,则形成最佳素数伴侣(先到先得);
                // 如果该位置偶数已经有了奇数伴侣,那么就看这个奇数是否还能找到其他的偶数伴侣,如果能找到,
                // 就将该位置偶数让出(能让就让)
                if (oushuSoul[i] == 0 ||
                        isPairSuccess(oushuSoul[i], oushu, oushuSoul, isPair)) {
                    // 将该位置偶数伴侣添加到数组进行配对成功
                    oushuSoul[i] = jishu;
                    // 找到最优的素数伴侣了
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * 判断一个数是否为素数.
     * 质数又称素数。一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数
     */
    private static boolean isPrimeNumber(int num) {
        for (int i = 2; i < num; i++) {
            // 在2-num之间,只要存在一个数,使得num能被这个数整除,则num为非素数
            if (num % i == 0 ) {
                return false;
            }
        }
        // 上面没有返回false,则表明不存在一个数,使得num能被这个数整除,则num为素数,则返回true
        return true;
    }
}



发表于 2023-02-10 16:31:24 回复(1)
我真的不知道为什么,做了一个晚上,优化了栈溢出、死循环和算法问题,现在就一个用例差一个实际输出,我debug都没法debug,我崩溃了。
发表于 2022-08-08 23:08:29 回复(1)
参考了toraoh的讲解.
import java.util.*;

public class Main {
    private static int res;
    private static List<List<Integer>> nextv;
    private static boolean[] vis;
    private static int[] match;
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        while (in.hasNext()) {
            int n = Integer.valueOf(in.nextLine());
            int[] nums = new int[n];
            for (int i = 0; i < n; i++) {
                nums[i] = in.nextInt();
            }
            res = 0;
            solve(nums);
            System.out.println(res);
        }
    }
    
    private static void solve(int[] nums) {
        int n = nums.length;
        if (n < 2) {
            return;
        }
        // 素数 = 奇数 + 偶数
        // 把奇数、偶数各分为一组;
        // 如果奇数和偶数能组成素数伴侣, 就连一条边
        List<Integer> odd = new ArrayList<>();
        List<Integer> even = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            if (nums[i] % 2 == 0) {
                even.add(i);
            } else {
                odd.add(i);
            }
        }
        if (odd.size() == 0 || even.size() == 0) {
            return;
        }
        // 建二分图
        nextv = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            nextv.add(new ArrayList<>());
        }
        for (int i = 0; i < odd.size(); i++) {
            int from = odd.get(i);
            for (int j = 0; j < even.size(); j++) {
                int to = even.get(j);
                int x = nums[from] + nums[to];
                if (isPrime(x)) {
                    nextv.get(from).add(to);
                }
            }
        }
        // 二分图最大匹配的匈牙利算法
        vis = new boolean[n];
        match = new int[n];
        for (int i = 0; i < n; i++) {
            match[i] = -1;
        }
        for (int i = 0; i < n; i++) {
            // 重置
            resetVis();
            if (dfs(i)) {
                res++;
            }
        }
    }
    
    private static void resetVis() {
        for (int i = 0; i < vis.length; i++) {
            vis[i] = false;
        }
    }
    
    private static boolean dfs(int from) {
        List<Integer> nextList = nextv.get(from);
        for (int i = 0; i < nextList.size(); i++) {
            int next = nextList.get(i);
            if (!vis[next]) {
                vis[next] = true;
                if (match[next] == -1) {
                    // 没有被匹配过
                    match[next] = from;
                    return true;
                } else {
                    // 被匹配过, 找增广路
                    boolean sgn = dfs(match[next]);
                    if (sgn) {
                        match[next] = from;
                        return true;
                    }
                }
            }
        }
        return false;
    }
    
    private static boolean isPrime(int x) {
        if (x <= 1) {
            return false;
        }
        if (x == 2) {
            return true;
        }
        for (int i = 2; i <= Math.sqrt(x); i++) {
            if (x % i == 0) {
                return false;
            }
        }
        return true;
    }
}


发表于 2022-05-30 18:09:15 回复(0)
亲测有效,采用匈牙利算法,在递归中腾挪,还有个注意点是,腾挪前(进入递归前),使用Set<index>初始化并记录腾挪过程已被匹配的下标,这些下标,在递归流程中将被跳过
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;

/**
 * @author pengxin
 * @date 2022/4/1 15:59
 */
public class Main {

    /**
     * 4
     * 2 5 6 13
     *
     * 2 6
     * 5 13
     *
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {


        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] ints = new int[n];
        for (int i = 0; i < n; i++) {
            ints[i] = sc.nextInt();
        }

        List<Integer> list1 = new ArrayList<>();
        List<Integer> list2 = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            if (ints[i] % 2 == 1) {
                list1.add(ints[i]);
            } else {
                list2.add(ints[i]);
            }
        }

        // 匹配数
        int result = 0;

        // list1 匹配 list2的记录:key-list2的值,value-list1的值
        Map<Integer, Integer> matchMap = new HashMap<>(n);

        int size1 = list1.size();
        for (int i = 0; i < size1; i++) {

            // 每次list1来匹配,list2腾挪过程中,已经被匹配的list2的index集合,这个集合中的下标,在递归流程中需要跳过
            Set<Integer> usedIndexSet = new HashSet<>();

            if (match(list1.get(i), list2, matchMap, usedIndexSet)) {
                result++;
            }
        }
        System.out.println(result);
    }

    private static boolean match(int int1, List<Integer> list2, Map<Integer, Integer> matchMap, Set<Integer> usedIndexSet) {

        int size2 = list2.size();
        for (int j = 0; j < size2; j++) {

            // 已经被匹配的list2的index集合,不需要再匹配
            if (usedIndexSet.contains(j)) {
                continue;
            }

            Integer int2 = list2.get(j);

            if (isMatch(int1 + int2)) {

                usedIndexSet.add(j);

                if (matchMap.get(int2) == null || match(matchMap.get(int2), list2, matchMap, usedIndexSet)) {
                    matchMap.put(int2, int1);
                    return true;
                }
            }
        }

        return false;

    }

    private static boolean isMatch(int x) {
        int sqrt = (int) Math.sqrt(x);
        for (int i = 2; i <= sqrt; i++) {
            if (x % i == 0) {
                return false;
            }
        }
        return true;
    }
}



发表于 2022-04-22 16:47:08 回复(1)
二分图,匈牙利算法,谢谢题解和讨论区的大佬
import java.util.*;
import java.io.*;

/**
 * 二分图,匈牙利算法
 */
public class Main{
    //参数中的偶数列表,作为全局变量便于传参
    private static ArrayList<Integer> evens = new ArrayList<>();
    
    public static void main(String[] args) throws IOException{
        //1.获取并存储数据
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        br.readLine();
        String[] nums = br.readLine().split(" ");
        ArrayList<Integer> odds = new ArrayList<>();//奇数列表
        //只有奇数和偶数的和才能为素数(除了2,条件已排除),正好适用二分图
        for(String a : nums){
            if(Integer.parseInt(a) % 2 == 0){//偶数
                evens.add(Integer.parseInt(a));
            }else{
                odds.add(Integer.parseInt(a));//奇数
            }
        }
        br.close();
        //2.数据准备
        //2.1定义一个偶数的伴侣数组,索引和偶数队列一一对应,值为伴侣的值,值为0表示没有伴侣
        int[] evenWife = new int[evens.size()];
        //2.2定义一个boolean数组,索引和偶数队列一一对应,记录一次奇数循环中已经匹配上的偶数
        boolean[] oddMatch;
        int count = 0;//统计伴侣对数
        
        //3.处理数据,为每一个奇数匹配伴侣
        for(int i=0; i<odds.size(); i++){//奇数循环,查找伴侣
            //每个奇数重置一遍,该数组作用避免死循环
            oddMatch = new boolean[evens.size()];
            //寻求最大路径,匈牙利算法
            if(find(odds.get(i), evenWife, oddMatch)){
                count++;
            }
        }
        System.out.println(count);
        
    }
    /*
     * 判断奇数x能否找到伴侣
     */
    private static boolean find(int x, int[] evenWife, boolean[] oddMatch){
        for(int i=0; i<evens.size(); i++){
            if(!oddMatch[i] && isPrime(evens.get(i) + x)){
                oddMatch[i] = true;//true表示该偶数i已被当前循环匹配为伴侣//防止闭环竞争
                /*
                 * 奇数x获得伴侣的两种情况:
                 * 1.偶数i没有伴侣==0,那就是我的了
                 * 2.偶数i有伴侣,但是i的伴侣(奇数)可以找到新的伴侣,那就把偶数i让出来(找不到就不让)
                 */
                if(evenWife[i] == 0 || find(evenWife[i], evenWife, oddMatch)){
                    evenWife[i] = x;//给偶数i添上伴侣
                    return true;
                }
            }
        }
        return false;
    }
     /*
     * 判断奇数x是否为素数
     */
    private static boolean isPrime(int x){
        for(int i=3; i<=Math.sqrt(x); i++){
            if(x%i == 0){
                return false;
            }
        }
        return true;
    }
    
    
}


发表于 2022-04-20 14:17:47 回复(0)
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * 匈牙利算法解决素数伴侣问题
 * 偶数+偶数一定不是素数,所以只能偶数和奇数匹配
 * 分为偶数,素数两个集合。找最大匹配
 */
public class Main {
    //定义偶数和奇数集合
    public static List<Integer> evenList;
    public static List<Integer> oddList;
    //判断奇数是否已访问过
    public static boolean[] vis;
    //存储奇数对应的偶数
    public static int[] p;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (sc.hasNextInt()){
            int n = sc.nextInt();
            //定义素数伴侣总数
            int count = 0;
            //初始化奇偶集合
            evenList = new ArrayList<>();
            oddList = new ArrayList<>();
            //初始化数组
            for (int i = 0; i < n;i++){
                int num = sc.nextInt();
                if (num % 2 == 0){
                    evenList.add(num);
                }else {
                    oddList.add(num);
                }
            }
            p = new int[oddList.size()];
            //循环每个偶数匹配
            for (int i = 0;i < evenList.size();i++){
                vis = new boolean[oddList.size()];
                if(match(evenList.get(i))){
                    count++;
                }
            }
            System.out.println(count);
        }
    }
    //匈牙利算法为当前偶数匹配素数,匹配到返回true,没匹配到返回false
    public static boolean match(int even){
        for (int i = 0; i < oddList.size();i++){
            //当前偶数奇数加起来是素数 并且 当前奇数未被访问
            if (isPrime(even+oddList.get(i)) && !vis[i]){
                //标记当前奇数已被访问
                vis[i] = true;
                //如果当前奇数还没有被匹配过,或者当前奇数匹配的偶数能另外匹配其它的奇数
                if (p[i] == 0 || match(p[i])){
                    //把当前奇数匹配的机会给当前偶数
                    p[i] = even;
                    return true;
                }
            }
        }
        //匹配不到,返回false
        return false;
    }

    //判断一个数是否是素数
    public static boolean isPrime(int num){
        if (num == 1){
            return false;
        }
        for (int i = 2; i*i <= num;i++){
            if (num % i == 0){
                return false;
            }
        }
        return true;
    }
}

发表于 2021-12-04 19:00:44 回复(0)
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * 素数伴侣
 * 分析:匹配问题
 * 奇数和偶数相加才可能出素数,因此奇数为图G1,偶数为图G2, 图内顶点互不相交关联
 * 算法:二分图+匈牙利算法+素数搜索
 * 素数用的是未被优化的筛选法,提前计算好
 * @author daryl.cai
 * @create 2021-10-13 10:19
 */
public class Main {

    static int max = 30000*2;//因为是两数加法操作
    //pri是合数
    static int[] pri = new int[max+1];


    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);
        prime();//先算max以内的素数

        while (scanner.hasNext()) {

            int num = scanner.nextInt();
            int n = num;
            ArrayList<Integer> odd = new ArrayList<>();
            ArrayList<Integer> even = new ArrayList<>();
            while ((--n) >= 0) {
                int t = scanner.nextInt();
                if (t % 2 == 0)
                    even.add(t);
                else
                    odd.add(t);
            }
            //匈牙利算法,传入图
            System.out.println(Hungarian(odd, even));

        }
    }

    /**
     *
     * @param odd 奇数在左侧
     * @param even 偶数在右侧
     * @return
     */
    private static int Hungarian(List<Integer> odd, List<Integer> even) {

        int cnt = 0;
        int oddLen = odd.size();
        int evenLen = even.size();
        boolean[][] map = new boolean[oddLen+1][evenLen+1];//存储图的关系
        boolean[] visit = new boolean[evenLen+1];//记录右侧元素是否被访问过
        int[] p = new int[evenLen+1]; //记录当前右侧元素所对应的左侧元素

        //初始化
        for (int i = 0; i <= oddLen; i++)
            for (int j = 0; j <= evenLen; j++)
                map[i][j] = false;
        for (int i = 0; i <= evenLen; i++) {
            visit[i] = false;
        }

        //构造二分图
        for (int i = 1; i <= oddLen; i++) {
            for (int j = 1; j <= evenLen; j++) {
                //组合成素数,注意-1操作,是为了避免数组越界问题
                if (pri[odd.get(i-1)+even.get(j-1)] == 0) {
                    map[i][j] = true;
                    //System.out.print(odd.get(i-1)+":"+even.get(j-1)+",");
                }
            }
        }
        //System.out.println();

        //匹配寻找
        for (int i = 1; i <= odd.size(); i++) {
            for (int k = 0; k <= evenLen; k++) {//重置,我做的时候,这里忘记写了,导致无法递归修改前置匹配
                visit[k] = false;
            }
            if (match(i, map, visit, p, evenLen, odd, even)) cnt++; //如果左侧i匹配了右侧数据
        }

//        System.out.println("even:"+evenLen);
//        for(int i = 1; i <= evenLen; i++) {
//            if (p[i] != 0)
//                System.out.println("match["+odd.get(p[i]-1)+":"+even.get(i-1)+"]");
//        }
        return cnt;//返回最大匹配
    }

    private static boolean match(int leftIndex, boolean[][] map, boolean[] visit, int[] p, int rightLen,
                                 List<Integer> odd, List<Integer> even) {
        for (int j = 1; j <= rightLen; j++) {
            if (map[leftIndex][j] && !visit[j]) {//有关联且未被访问
                visit[j] = true;//标记访问
                if (p[j] == 0 || match(p[j], map, visit, p, rightLen, odd, even)) {
                    //如果右侧p[j]还没有被左侧匹配,或者原来匹配的左侧元素p[j]可以找到新的匹配(即p[j]先让一下leftIndex)
                    p[j] = leftIndex;

                    //System.out.println("[l:"+odd.get(leftIndex-1)+","+even.get(j-1)+"]");
                    return true; //代表leftIndex匹配了j
                }
            }
        }
        return false;//循环结束,仍未找到匹配
    }

    private static void prime() {
        pri[1] = 1;
        pri[0] = 1;
        for (int i = 2; i <= max/2; i++) {
            for(int j = i*2; j <= max; j+=i) {
                pri[j] = 1;
            }
        }
    }
}
发表于 2021-10-13 12:20:43 回复(0)
简单粗暴的玩法
* 将备选数字根据奇数偶数分成A,B两组, 1, 遍历A、B组成员记录其伴侣数量和伴侣列表,
 * 2,将A组中单伴侣成员放入Aa组,多伴侣放入AA组,B组成员一样放入Bb组和BB组
 * 3,Aa和Bb组找到成员多的largerUni组,其对应的多伴侣组为largerMuti组,相应的有smallerUni组和samllerMuti组,
 * 1,对largerUni组进行遍历,查看其伴侣是否存在: 如存在则count增加1,同时将这一对伴侣移除出组,
 * 移除时如果其伴侣有多个伴侣,则要顺带解除其他伴侣的关系。 2,对smallerUni组进行3-1相同操作
 * 4,此时只需关注largerMuti,samllerMuti组,将其作为A组和B组重复2,3操作,直到2操作后Aa组Bb组都为空,循环结束。
 * 5,循环结束后如果AA,BB组也为空,则直接结束,AA,BB组不为空,则直接在伴侣对数上加上,[取小(AA.length,BB.length)]
发表于 2021-10-12 20:30:18 回复(0)
這個答案是不是錯的?
发表于 2021-10-08 21:16:48 回复(0)
import java.io.*;
import java.util.*;
public class Main{
    public static void main(String[] args)throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String str = null;
        while((str = br.readLine())!=null){
            int N = Integer.parseInt(str);
            String[] inputs = br.readLine().split(" ");
            int[] num = new int[N];
            int k1 = 0;
            for(String input : inputs){
                num[k1++] = Integer.parseInt(input);
            }
            int[] oushu = new int[N];
            int[] jishu = new int[N];
            int k2 = 0;
            int k3 = 0;
            for(int i = 0 ; i < num.length ; i++){
                if(num[i]%2!=0){
                    jishu[k2++] = num[i];
                }else{
                    oushu[k3++] = num[i];
                }
            }
            int[] match = new int[oushu.length];
            int res = 0;
            for(int i = 0 ; i < jishu.length ; i++){
                boolean[] used = new boolean[oushu.length];
                if(find(jishu[i],oushu,used,match)){
                    res++;
                }
            }
            System.out.println(res);
        }
    }
    
    //如果第j个偶数没有伴侣,则直接奇数x设置为他的伴侣;
    //否则,第j个偶数原来有伴侣的话,
    //如果他的原伴侣能够重新找到伴侣,则奇数x设置为第j个偶数的伴侣(递归。。)
    public static boolean find(int x,int[] oushu,boolean[] used,int[] match){
        for(int i = 0 ; i < oushu.length ; i++ ){
            if(isPrime(x+oushu[i]) && !used[i]){
                //x看上oushu[i]了
                used[i] = true;
                //oushu[i]是单身  或者 oushu[i]的原对象把他甩了
                if(match[i]==0 || find(match[i],oushu,used,match)){
                    //x和oushu[i]在一起了,登记到match
                    match[i] = x;
                    return true;
                }
            }
        }
        return false;
    }
    //判断是否为素数
    public static boolean isPrime(int num){
        for(int i = 2 ; i < Math.sqrt((double)num) ; i++){
            if(num%i==0){
                return false;
            }
        }
        return true;
    }
}

按照评论区的把自己的改了一下  不知道哪里错了 运行的结果不对 

发表于 2021-04-09 13:12:24 回复(0)
package hwtest.boy_girl;

import java.io.IOException;
import java.util.*;

/**
 * @author 史新刚 xgNLP
 * @version Main,  2020/9/23 11:15
 */
public class Main {
static int[][] flags = null;
static int[] g_boy = null;
static int[] b_girl = null;
static Map<Integer, ArrayList<Integer>> map = new HashMap<>();
static int count=0;
static int[] visited =null;
static boolean isPrime(int d) {
    if(d==2)return true;
    for (int i = 2; i <= Math.round(Math.sqrt(d)); i++) {
        if (d % i == 0) return false;
    }
    return true;
}


public static void main(String[] args) throws IOException {
    Scanner sc = new Scanner(System.in);
    while (sc.hasNextLine()) {
        map.clear();
        String line = sc.nextLine();
        int n = Integer.parseInt(line);
          line = sc.nextLine();
        String[] ss = line.split(" ");
        ArrayList<Integer> odd = new ArrayList<>();
        ArrayList<Integer> even = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            try {
                int _a = Integer.parseInt(ss[i]);
                if (_a % 2 == 0) {
                    even.add(_a);
                } else {
                    odd.add(_a);
                }
            }catch (Exception e){}
        }
        if (odd.size() > even.size()) {
            ArrayList _c = odd;
            odd = even;
            even = _c;
        }//奇的多 交换
        //标志
        flags = new int[odd.size()][even.size()];
        g_boy = new int[even.size()];
        b_girl = new int[odd.size()];
      visited=  new int[even.size()];
        Arrays.fill(visited, 0);
//        Arrays.fill(flags, 0);
        Arrays.fill(g_boy, -1);
        Arrays.fill(b_girl, -1);
        for (int i = 0; i < odd.size(); i++) {
            for (int j = 0; j < even.size(); j++) {
                if (isPrime(odd.get(i) + even.get(j))) {
                    flags[i][j] = 1;
                    if (map.containsKey(i)) {
                         List<Integer> _list = map.get(i);
                       if(_list!=null)map.get(i).add(j);
                    }else{
                        ArrayList<Integer> _lst = new ArrayList<>();
                        _lst.add(j);
                        map.put(i, _lst);
                    }
                }
            }
        }
          List<Map.Entry<Integer, ArrayList<Integer>>> entries = new ArrayList<Map.Entry<Integer, ArrayList<Integer>>>(map.entrySet());
       if(!entries.isEmpty()) Collections.sort(entries, new Comparator<Map.Entry<Integer, ArrayList<Integer>>>() {
            @Override
            public int compare(Map.Entry<Integer, ArrayList<Integer>> o1, Map.Entry<Integer, ArrayList<Integer>> o2) {
                return Integer.compare(o1.getValue().size(),o2.getValue().size());
            }
        }); //少边的点优先
        //读入数据
        //分两类 girls,boys,少的算boys
        //定义 二维数组 flag[boys][girls.length] 全置false
        //遍历 算出对应的边 置true
        //遍历boy
        //初始化访问状态,用于记录下访问了哪些boy 节点
         count=0;
        for (Map.Entry<Integer, ArrayList<Integer>> entry : entries) {
            int i=entry.getKey();  //皆为index
            Arrays.fill(visited,0);
            if(find(i)){
                count++;
            }
        }
        System.out.println(count);
        //遍历它的状态 flag[i][x],若为1且,girl_used[x] 没有占用  ,标上占用 girl_used[x]  g_boy[x]=i  记上它用的是哪个boy
        //若占用了此女孩,将此女孩的g_boy[x]  vis[x]=true, 不能再打这个girl主意,避免死循环。
        //return true;
    }
}
static boolean find(int i) {
    ArrayList<Integer> lines=map.get(i) ;
    for (Integer j : lines) {
        if(visited[j]==0) { //递归时,没有访问到
            visited[j]=1;
            if (g_boy[j] == -1) {   //未占用
                g_boy[j] = i;
                b_girl[i] = j;
                return true;
            } else {  //女孩已嫁
                if (find(g_boy[j])) {
                    g_boy[j] = i;
                    b_girl[i] = j;
                    return true;
                }
            }
        }
    }
    return false;
}


public static void mapAdd(char a, Map<Byte, Integer> map) {
    byte c = (byte) a;
    if (map.containsKey(c)) {
        map.put(c, (map.get(c) + 1));
    } else {
        map.put(c, 1);
    }
}
}

发表于 2020-09-25 14:14:42 回复(1)