首页 > 试题广场 >

素数伴侣

[编程题]素数伴侣
  • 热度指数:190586 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32M,其他语言64M
  • 算法知识视频讲解
\hspace{15pt}定义两个正整数 ab 是“素数伴侣”,当且仅当 a + b 是一个素数。
\hspace{15pt}现在,密码学会邀请你设计一个程序,从给定的 n 个正整数 \left\{a_1, a_2, \dots, a_n\right\} 中,挑选出最多的“素数伴侣”,你只需要输出挑选出的“素数伴侣”对数。保证 n 为偶数,一个数字只能使用一次。

输入描述:
\hspace{15pt}第一行输入一个正偶数 n \left(2 \leqq n \leqq 100\right) 代表数字个数。
\hspace{15pt}第二行输入 n 个正整数 a_1, a_2, \dots, a_n \left(1 \leqq a_i \leqq 3 \times 10^4 \right) 代表给定的数字。


输出描述:
\hspace{15pt}输出一个整数,代表最多可以挑选出的“素数伴侣”的数量。
示例1

输入

4
2 5 6 13

输出

2

说明

\hspace{15pt}在这个样例中,25 可以组成一对素数伴侣,613 也可以组成一对素数伴侣。因此,最多可以挑选出 2 对素数伴侣。
示例2

输入

4
1 2 2 2

输出

1

说明

\hspace{15pt}在这个样例中,1 只能使用一次,与任意一个 2 组合均是“素数伴侣”。
示例3

输入

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 回复(48)
一种比较暴力的算法把所有可能的组合都列出来,然后算没有重复的组合里哪种最长
但是只有前7组能在规定时间内算完
import sys
import math

def isPrime(p):
    for i in range(int(math.sqrt(p))):
        if (p%(i+2)==0): return False
    return True

num=int(sys.stdin.readline())
a = sys.stdin.readline().split()
a=[int(x) for x in a]

c1=[]
c2=[]
for i in range(num-1):
    for j in range(num-i):
        if isPrime(a[i]+a[i+j]):
            c1.append(a[i])
            c2.append(a[i+j])

num_couple=len(c1)
maximum=0

for i in range(int(math.pow(2,num_couple)),0,-1):
    d=[]#place holder for checking if a number is already used
    for j in range(num_couple):
        if int(i/math.pow(2,j))%2==1:
            if not(c1[j] in d&nbs***bsp;c2[j] in d):
                d.append(c1[j])
                d.append(c2[j])
    if len(d)<=num and len(d)>maximum:
        maximum=len(d)
print(int(maximum/2))

发表于 2025-01-06 12:33:46 回复(0)
记录一下踩的坑,本题中的数字是可能重复出现的,比如下面的测试用例中的18853,所以用集合存储顶点会导致错误结果,咳咳。
32
25606 9056 17585 9754 29060 978 3156 9997 13286 3419 18853 5325 1580 292 24811 943 18898 6507 6270 7296 15538 20251 28206 10001 818 3953 993 15744 8489 20700 18853 24969


发表于 2024-07-16 11:35:52 回复(0)
from math import sqrt


def is_prime(num: int) -> bool:
    if num < 2:
        return False
    elif num == 2:
        return True
    else:
        for i in range(2, int(sqrt(num) + 1)):
            if num % i == 0:
                return False
        return True


def mate(
    odd: int,
    final_selection: list[int],
    temporary_selection: list[int],
    evens: list[int],
):
    for i in range(len(evens)):
        if is_prime(odd + evens[i]) and temporary_selection[i] == -1:
            temporary_selection[i] = 1  # 1只表示evens[i]暂时被选了
            if final_selection[i] == -1&nbs***bsp;mate(
                final_selection[i], final_selection, temporary_selection, evens
            ):
                final_selection[i] = odd
                return True
    return False


n = int(input())
numbers = [int(i) for i in input().strip().split()]

# 奇数+奇数=偶数,偶数+偶数=偶数,都不能成为素数。只能奇数+偶数的组合才有可能
# 第一步:分奇偶
odds, evens = [], []
for num in numbers:
    if num % 2 == 0:
        evens.append(num)
    else:
        odds.append(num)
# 第二步:二分图最佳匹配
count = 0
final_selection = [-1] * len(evens)
for odd in odds:
    temporary_selection = [-1] * len(evens)
    if mate(odd, final_selection, temporary_selection, evens):
        count += 1
print(count)


# 考察点:匈牙利算法(二分图最佳匹配算法)

发表于 2023-09-13 16:08:39 回复(0)
这个题要命了

发表于 2023-05-29 16:36:37 回复(0)
import math
def check(n):
    for i in range(2,int(math.sqrt(n))+1):
        if n % i == 0:
            return False
    return True
def find(odd,visited,choose,evens):
    for j,even in enumerate(evens):
        if check(odd+even) and not visited[j]:
            visited[j] = True
            if choose[j] == 0 or find(choose[j],visited,choose,evens):
                choose[j] = odd
                return True
    return False
        
while True:
    try:
        m = int(input())
        new = list(map(int,input().split()))
        evens = []
        odds = []
        count = 0
        for i in new:
            if i % 2 == 0:
                odds.append(i)
            else:
                evens.append(i)
        choose = [0]*len(evens)
        for odd in odds:
            visited = [False] * len(evens)
            if find(odd,visited,choose,evens):
                count += 1
        print(count)          
    except:
        break
发表于 2022-09-09 07:21:20 回复(0)
def issu(x):
    tem = 2
    while tem ** 2 < x+1:
        if x % tem == 0:
            return False
        tem += 1
    return True

def find(a,list1,list2,list3):
    for i in range(len(list3)):
        if issu(a + list3[i]) and list1[i] == 0:
            list1[i] = 1
            if list2[i] == 0&nbs***bsp;find(list2[i],list1,list2,list3):
                #上边,如果list2【i】不为零,则将该位置上的奇数,和本次剩余的偶数进行匹配,
                #如果找的到,占用此数的位置,否则返回失败
                list2[i] = a
                return True
    return False
while True:
    try:
        n = int(input())
        l = list(map(int,input().strip().split()))
        j = [] #奇数队
        o = [] # 偶数队
        for i in range(n):
            if l[i] % 2 == 0:
                o.append(l[i])
            else:
                j.append(l[i])
        count = 0
        match = [0] * len(o)
        for i in range(len(j)):
            used = [0] * len(o)
            if find(j[i],used,match,o):
                count += 1#每次配对成功一次进行记数一次
        print(count)
    except:
        break

发表于 2022-07-03 00:02:21 回复(0)
是我的理解有问题吗,2也是素数啊,那要是有重复1的情况呢,还是题目中说了不允许是重复的数
发表于 2022-06-13 16:30:25 回复(0)
在这上面提交,部分结果总是少1
而在Pycharm 运算是正常的。
import itertools
n=int(input().strip())
s=input().split()
s=[str(i) for i in s]
lst=list(itertools.permutations(s,4))
def sushu(n:int)->bool:
    count=0
    for i in range(1,n+1):
        if n%i==0:
            count+=1
    if count<3:
        return True
geshu=[]
for i in range(len(lst)):
    he=0
    for j in range(0,len(lst[0]),2):
        
        if sushu(int(lst[i][j])+int(lst[i][j+1])):
            he+=1
    geshu.append(he)
if len(geshu)>0:    
    print(max(geshu))        
else:
    print(0)


发表于 2022-05-10 21:55:37 回复(0)
数据里面有重边,导致调试了好久
发表于 2022-04-02 09:13:32 回复(0)
哎,想的太简单了,一开始只能即通过一部分案例,回头再看题目,
才发现已经用过的数不能再用,然后再改,后来又发现一个点可能有多个点配合,但是要要让总数尽可能多,然后接歇菜了,各种遍历、倒叙,整不出来,
看了讨论区,啥匈牙利算法、红黑树算法,俺没学过呀。等到考试遇到这个,真可就麻烦了,直接挂了就
发表于 2022-03-26 16:03:10 回复(0)

了解匈牙利算法的思想以后终于能看懂大佬们的题解了。手打一遍并记下实现过程。

匈牙利算法思想:https://blog.csdn.net/sunny_hun/article/details/80627351

实现要点:

1.奇偶分组:
用偶数组当做待选列表,每次拿一个奇数,到偶数列表中选对象。

2.两个工具:
-->占用表ocu 随着每个奇数重新初始化,在每个奇数配对的单轮循环内标记已被占用的偶数(0代表可用,1代表占用)。

-->配对表mtch 只初始化一次,标注j下标的偶数配给了哪个奇数。
配对表随着奇数检查即时更新。

3.find函数
角色:承载检查目标奇有无配对状态,并在检查过程中更新配对表
传入:目标奇,占用表,配对表,偶数列表
返回:能(True)否(False)配对
配对规则:如果配对表记录当前偶无对象,或者有对象(不是目标奇,简称原配奇)但是有其他偶数能挪给原配奇,就让目标奇配当前偶

以上几个要点清晰了以后,整个方案就清楚了。

def ispair(x:int, y:int)->bool:
    n,m = int((x+y)**0.5)+1, x+y
    for i in range(2,n):
        if m%i==0: return False
    return True
def find(odd:int, ocu:list, mtch:list, even:list)->bool:
    for j in range(len(even)):
        if ispair(odd,even[j]) and ocu[j]==0:  #如果当前偶跟目标奇能配对,而且当前偶还没被登记占用,就把当前偶占住
            ocu[j]=1
            if mtch[j]==0 or find(mtch[j],ocu,mtch,even):  #如果配对表记录当前偶没对象,或者有对象(不是目标奇,简称原配奇)但是有其他偶数能挪给原配奇,就让目标奇配当前偶
                mtch[j]=odd
                return True
    return False
while True:
    try:
        n,arr,odd,even = int(input()),list(map(int,input().split())),[],[]
        for i in range(n):
            if i%2==0: even.append(i)
            else: odd.append(i)
        mtch = [0]*len(even)
        result = 0
        for i in range(len(odd)):  #遍历odd列表
            ocu = [0]*len(even)  #对于每个奇数,首先初始化现有的偶数占用方案,然后调用find函数看能不能找到配对偶数
            if find(odd[i],ocu,mtch,even):
                result += 1
        print(result)
    except:
        break
发表于 2022-02-04 13:08:56 回复(0)
import itertools
while True:
    try:
        import itertools

        n = int(input())
        s = [int(i) for i in input().split()]


        def isprime(x):
            flag = True
            for i in range(2, x):
                if x % i == 0:
                    flag = False
                    break
            return flag


        def iscouple(x, y):
            return isprime(x + y)


        l1 = []
        l2 = []
        for i in s:
            if i % 2 != 0:
                l1.append(i)
            else:
                l2.append(i)

        if len(l1) < len(l2):
            tmp = l1
            l1 = l2
            l2 = tmp
        ls = []
        for i in l2:
            for j in l1:
                if iscouple(i, j):
                    ls.append([i, j])

        lc = []
        for x in itertools.permutations(l1, len(l2)):
            count = 0
            lt = []
            for i in range(len(l2)):
                lt.append([l2[i], x[i]])
            lr = [val for val in ls if val in lt]
            lc.append(len(lr))
            if len(lr) == len(l2):
                break

        print(max(lc))
    except:
        break
                    

            
        
                    
                
                
                
                
                
                
想通过暴力计算,排列组合的方式解决,理论上可以解决,可惜算法太耗时
发表于 2021-12-10 18:45:21 回复(0)
def su(a):
    tem = 2
    while tem**2 <= a:
        if a%tem == 0:
            return False
        tem += 1
    return True

def fmatrix(a,b):
    m = [[0 for _ in b] for _ in a]
    for ii, i in enumerate(a):
        for jj, j in enumerate(b):
            if su(i+j):
                m[ii][jj] = 1
    return m

def find(x):
    for i in range(len(odd)):
        if matrix[x][i] == 1 and used[i] == 0:
            used[i] = 1
            if match[i] == -1&nbs***bsp;find(match[i]) != 0:
                match[i] = x
                return True
    return False

while True:
    try:
        n = int(input())
        nums, odd, even = list(map(int,input().split())), [], []
        count = 0
        for i in nums:
            if i%2 == 0:
                even.append(i)
            else:
                odd.append(i)
        matrix = fmatrix(even,odd)
        match = [-1]*len(odd)
        for i in range(len(even)):
            used = [0]*len(odd)
            if find(i):
                count += 1
        print(count)
    except:
        break
看了了多个评论又自己在网上找了一些资料才搞懂了思路与匈牙利算法,自己写了一遍,用例全过
发表于 2021-10-05 19:19:59 回复(0)

问题信息

难度:
14条回答 58127浏览

热门推荐

通过挑战的用户

查看代码
素数伴侣