首页 > 试题广场 >

素数伴侣

[编程题]素数伴侣
  • 热度指数:190877 时间限制: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)
有没有大神帮忙看一下,能不能优化一下
n = int(input())
s = list(map(int, input().split()))
count = 0
i = 0

while i < n - 1:
    m = s[i] + s[i+1]
    if m == 2&nbs***bsp;m == 3:
        count += 1
        i += 2
        continue

    if m % 2 == 0:
        i += 1
        continue

    is_prime = True
    
    for j in range(2, int(m**0.5)+1):
        if m % j == 0:
            is_prime = False
            break

    if is_prime:
        count += 1
        i += 2
    else:
        i += 1

print(count)


发表于 2025-07-10 16:39:14 回复(0)
第一个sample里面5和6不是素数伴侣吗?
发表于 2025-05-21 17:33:17 回复(0)
#!/user/local/bin/python3
import math
def is_prime(num):   #判断是否是质数
    if num<2:
        return False
    if num==2:
        return True
    pnum=int(math.sqrt(num))+1
    for i in range(2,pnum):
        if num%i==0:
            return False
    return True

def find_element(eles,target):  ##查找元素在列表中第一次出现的索引,没有找到返回-1
    for i in range(len(eles)):
        if eles[i].count(target) > 0:
            return i
    return -1

def takeSecond(elem):
    return elem[1]

strnumbs=int(input())
sources=list(map(int,input().split()))

#思路:除了2,所有的质数都是一个奇数+一个偶数,所以可以分成两个队列进行配对。
#双重循环,找出所有元素所有可能的配对情况,有一个配对加一分。按照分数,从低到高排序。所有可能的配对结果放在配对池子中。
#1.分数为0的元素不能配对,从分数排行榜中删除。
#2.优先满足分数少的元素形成配对,配对以后,配对结果放在成果池子中,配对结果从配对池子中删除,配对的两个元素的分数都减一。
#3.如果数字X放入成果池子中的次数等于X在原始列表中的数量(考虑X在原始列表中有重复的情况),清空配对池子中所有包含X的配对,将X的分数清零。
#刷新分数排行榜,重复上述(1)(2)(3)步骤,直到分数排行榜为空或配对池子为空。

#除了2,所有的质数都是一个奇数+一个偶数,所以可以分成两个队列
odd=[item for item in sources if item%2!=0]  #奇数队列
even=[item for item in sources if item%2==0]  #偶数队列
odd.sort()
even.sort()

final_result=[]     #成果池子
backup_result=[]   #配对池子
sourcelist=[[elem,0,0] for elem in sources] #分数排行榜,第二列代表配对分数,第三列代表是否已经放入成果池子

##初始化分数排行榜
for i in range(len(odd)):
    for j in range(len(even)):
        if is_prime(odd[i]+even[j]): ##找到配对
            backup_result.append([odd[i],even[j]])
            sourcelist[find_element(sourcelist,odd[i])][1]+=1
            sourcelist[find_element(sourcelist,even[j])][1]+=1
sourcelist.sort(key=takeSecond)

i=0
while i < len(sourcelist) and len(backup_result)>0:
    if sourcelist[i][1]==0:
        sourcelist.pop(i) ##删除分数排行榜中无法配对的元素
        continue
    else:
        while sourcelist[i][1]>0:
            ##找到第一个配对的元素的在配对池子中的索引
            xindex=find_element(backup_result,sourcelist[i][0])
            ##找到第一个配对中另一个元素在分数排行榜中的索引
            if sourcelist[i][0]%2==0:
                another_index=find_element(sourcelist,backup_result[xindex][0])
            else:
                another_index=find_element(sourcelist,backup_result[xindex][1])
            sourcelist[i][1]-=1 ##配对的第一个元素,分数减一
            sourcelist[another_index][1]-=1 ##配对的第二个元素,分数减一
            final_result.append(backup_result[xindex]) ##将配对结果放入成果池子中
            sourcelist[i][2]=1 ##记录该元素放入成果池子中的次数
            sourcelist[another_index][2]=1 ##记录该元素放入成果池子中的次数
            backup_result.pop(xindex) ##删除配对池子中的该配对

            #如果元素X放入成果池子中的次数等于X在原始列表中的数量(考虑X在原始列表中有重复的情况),清空配对池子中所有包含X的配对,将X的分数清零。
            if sourcelist[i][2] == sources.count(sourcelist[i][0]):
                while find_element(backup_result,sourcelist[i][0])!=-1: 
                    popone=backup_result.pop(find_element(backup_result,sourcelist[i][0])) #清空配对池子中所有包含X的配对
                    find_index=find_element(sourcelist,popone[0]) ##清空配对的时候,对应的元素分数减一
                    sourcelist[find_index][1]-=1
                    find_index2=find_element(sourcelist,popone[1])
                    sourcelist[find_index2][1]-=1
                sourcelist[i][1]=0 ##X的分数清零,不再参与配对

            if sourcelist[another_index][2] == sources.count(sourcelist[another_index][0]):
                while find_element(backup_result,sourcelist[another_index][0])!=-1:
                    popone=backup_result.pop(find_element(backup_result,sourcelist[another_index][0]))
                    find_index=find_element(sourcelist,popone[0])
                    sourcelist[find_index][1]-=1
                    find_index2=find_element(sourcelist,popone[1])
                    sourcelist[find_index2][1]-=1
                sourcelist[another_index][1]=0
        sourcelist.sort(key=takeSecond)  #刷新分数排行榜
            
#print("Final Result:",final_result)
print(len(final_result))

发表于 2024-12-14 16:41:22 回复(0)
# 最大匹配问题,尤其是在 二分图 中,通常不通过传统的动态规划方法来解决。相反,最大匹配问题更适合使用 图论算法,如 匈牙利算法 或 Hopcroft-Karp算法。

# 二分图匹配问题
import math

# 判断一个数是否为素数
def is_prime(x):
    if x < 2:
        return False
    if x == 2:
        return True
    for i in range(3, int(math.sqrt(x)) + 1,2):
        if x % i == 0:
            return False
    return True

# 匈牙利算法核心函数,尝试为偶数节点找到一个匹配的奇数节点
def bpm(u, graph, seen, match_to_V):
    for v in graph[u]:
        if not seen[v]:
            seen[v] = True
            # 如果奇数节点v未被匹配,或者可以为其找到其他匹配
            if match_to_V[v] == -1&nbs***bsp;bpm(match_to_V[v], graph, seen, match_to_V):
                match_to_V[v] = u
                return True
    return False

# 主函数,处理输入并寻找最大匹配对数
def max_prime_pairs(nums):
    # 将输入分为奇数和偶数
    odds = []
    evens = []
    for num in nums:
        if num % 2 == 0:
            evens.append(num)
        else:
            odds.append(num)
    
    # 构建二分图的邻接表,graph[u] 存储偶数u能配对的奇数v的索引
    graph = [[] for _ in range(len(evens))]
    for i, even in enumerate(evens):
        for j, odd in enumerate(odds):
            if is_prime(even + odd):
                graph[i].append(j)
    
    # 初始化匹配数组,-1表示未匹配
    match_to_V = [-1] * len(odds)
    result = 0
    
    # 对每个偶数节点尝试找到匹配
    for u in range(len(evens)):
        seen = [False] * len(odds)
        if bpm(u, graph, seen, match_to_V):
            result += 1
    
    return result

# 读取输入
if __name__ == "__main__":
    import sys
    input = sys.stdin.read().split()
    ptr = 0
    while ptr < len(input):
        if ptr >= len(input):
            break
        n = int(input[ptr])
        ptr += 1
        if ptr + n > len(input):
            break
        nums = list(map(int, input[ptr:ptr + n]))
        ptr += n
        # 调用函数并输出结果
        print(max_prime_pairs(nums))

发表于 2024-09-23 19:09:15 回复(1)
求助大家,有一组样例无法通过
n = int(input())
arr = list(map(int, input().split()))


n1,n2 = [],[]
g = {}
st = [False]*30010
match = [0]*30010
res = 0
# 二分图的最大匹配数, 关键点在于点集的选取

def is_prime(num):
    if num <= 1:
        return False
    for i in range(2, int(num**0.5) + 1):
        if num % i == 0:
            return False
    return True

def find(x):
    global g,match,st
    for j in g[x]:
        if not st[j]:
            # 标记n2点已经被遍历过
            st[j] = True
            if match[j] == 0&nbs***bsp;find(match[j]):
                match[j] = x
                return True
    return False

# 建图
for i in range(len(arr)):
    for j in range(len(arr)):
        if is_prime(arr[i] + arr[j]):
            if arr[i] not in g.keys():
                g[arr[i]] = [arr[j]]
            else:
                g[arr[i]].append(arr[j])

# 二分
for a in g.keys():
    # 可以分为奇数与偶数
    if a%2 == 1:
        n1.append(a)
    else:
        n2.append(a)
# 匈牙利算法得最大值,将不能作为素数伴侣的点作为一边
for i in range(len(n1)):
    # 每次遍历时初始化st数组为False;!!!st[j]存的是右半部分的节点j是否被访问过
    st = [False]*30010
    if find(n1[i]):
        res+=1

print(res)


发表于 2024-04-22 22:24:53 回复(0)
import sys


"""
匈牙利算法:先到先得,能让则让
"""

n=int(input())
data=list(map(int,input().split()))
# print(data)
def odd_is_prime(odd):
    """
    只可能奇数+偶数为素数,奇数+偶数又是奇数,所以只用判断奇数
    """
    for i in range(3,int(odd**0.5)+2,2):
        if odd%i==0:
            return False
    return True

def matrix(odds,evens):
    """
    素数矩阵,能配对的置为1
    """
    l=[[0 for _ in evens] for _ in odds]
    for r,o in enumerate(odds):
        for c,e in enumerate(evens):
            if odd_is_prime(o+e):
                l[r][c]=1
    return l

def find(oi,evens,matrix,used_e,connect_e):
    """
    给奇数配对
    """
    for ei in range(len(evens)):
        # 可以配对,而且偶数没有被使用过
        if matrix[oi][ei]==1 and used_e[ei]==0:
            used_e[ei]=1  # 标记一下,此偶数被使用了
            # 如果偶数未被配对,或者 被配对的奇数还可以找到其他偶数配对
            # connect_e[] 里放的是配对的奇数
            if connect_e[ei]==-1&nbs***bsp;find(connect_e[ei],evens,matrix,used_e,connect_e):
                connect_e[ei]=oi
                return 1
    return 0

odds=[]
evens=[]

# 奇偶分组
for e in data:
    if e%2==0:
        evens.append(e)
    else:
        odds.append(e)

m=matrix(odds,evens)

connect_e=[-1 for _ in evens]  

count=0

for oi in range(len(odds)):
    used_e=[0 for _ in evens]
    if find(oi,evens,m,used_e,connect_e):
        count+=1
print(count)















































发表于 2024-04-11 18:10:30 回复(0)
import math
n=int(input())
a=input().split(' ')
num=list(map(int,a))
od=list(filter(lambda x:x%2==1,num))
ev=list(filter(lambda x:x%2==0,num))
def check(num):
    for i in range(2,int(math.sqrt(num)+2)):
        if num%i==0:
            return False
    return True
s=[[0]*len(od) for i in range(len(ev))]

for i in range(len(ev)):
    for j in range(len(od)):
        if check(ev[i]+od[j]):
            s[i][j]=1
aa=[]
def find(lodd,choose,evei,s,ss):
    for j in range(lodd):
        if s[evei][j]==1 and ss[j]==0:
            ss[j]=1
            if j not in choose&nbs***bsp;find(lodd,choose,choose.index(j),s,ss):
                if evei>=len(choose):
                    choose.append(j)
                else:
                    choose[evei]=j
                return True
    return False
con=0
for i in range(len(ev)):
    ss=[0]*len(od)
    if find(len(od),aa,i,s,ss):
        con+=1
print(con)

编辑于 2024-04-01 21:57:11 回复(0)
def is_prime(num):
    if num <= 1:
        return False
    if num == 2:
        return True
    if num % 2 == 0:
        return False
    for i in range(3, int(num ** 0.5) + 1, 2):
        if num % i == 0:
            return False
    return True

def find_prime_pairs_count(numbers):
    n = len(numbers)
    match = [-1] * n  # 存储匹配的数字,初始化为-1表示未匹配
    visited = [False] * n  # 记录数字是否已经匹配

    def dfs(x):
        for y in range(n):
            if not visited[y] and is_prime(numbers[x] + numbers[y]):
                visited[y] = True
                if match[y] == -1 or dfs(match[y]):
                    match[y] = x
                    return True
        return False

    count = 0
    for i in range(n):
        visited = [False] * n
        if dfs(i):
            count += 1

    return count // 2  # 每对数字会被计算两次,所以需要除以2

# 读取输入
n = int(input())
input_numbers = list(map(int, input().split()))

# 处理数据并输出结果
result = find_prime_pairs_count(input_numbers)
print(result)

发表于 2023-08-30 19:37:50 回复(0)
from math import sqrt

def is_prime(x):   # 判断素数
    if x == 2:
        return True
    else:
        for i in range(2,int(sqrt(x))+1):
            if x % i == 0:
                return False
        return True

# 匈牙利算法 :用奇数挨个匹配偶数,偶数没有素数伴侣直接被匹配,有的话问问这个这个偶数当前的素数伴侣能不能让让
def f1(m:int,lr:list[list[int]],vis:list[int]):
    for j in range(len(lr)):
        if vis[j]==1:
            continue
        if is_prime(m+lr[j][0]):
            vis[j] = 1    # 这里必须先吧vis[j] 置为1 不然容易死循环
            if lr[j][1] == 0&nbs***bsp;f1(lr[j][1],lr,vis) :
                lr[j][1] = m  
                return True
    return False

n = int(input())
l = list(map(int,input().split(' ')))
ll = []   # 奇数列表
lr = []   # 偶数及其伴侣列表
for item in l:
    if item % 2 == 0:
        lr.append([item, 0])  # 第二位储存 当前偶数的素数伴侣
    else:
        ll.append(item)

count = 0
for item in ll:
    vis = [0] * len(lr)
    if f1(item,lr,vis):
        count += 1

print(count)


发表于 2023-07-19 00:49:50 回复(0)
def isprime(n):
    if n<2:
        return False
    for i in range(2,int(n**(1/2))+1):
        if n%i==0:
            return False
    return True
input()
ipt = map(int, input().split())
even, odd = [], []
for i in ipt:
    if i % 2 == 0:
        even.append(i)
    else:
        odd.append(i)

match=[-1 for j in range(len(odd))]

def fn(i):
    for j in range(len(odd)):
        if isprime(even[i]+odd[j]) and j not in v:
            v.add(j)
            if match[j]==-1&nbs***bsp;fn(match[j]):
                match[j]=i
                return True
    return False
ans=0
for i in range(len(even)):
    v=set()
    if fn(i):
        ans+=1
print(ans)

发表于 2023-06-02 13:07:09 回复(0)