首页 > 试题广场 >

素数伴侣

[编程题]素数伴侣
  • 热度指数:163635 时间限制: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)
代码都写完了才发现是不重复组合?
发表于 2021-04-11 16:05:16 回复(0)
到目前为止最难的一道题目
def is_zhi(n):
    for i in range(2, int(n**0.5)+1):
        if not n % i:
            return False
    return True


def find_pair(n, even, visited, match):
    for i, e in enumerate(even):
        if is_zhi(n+e) and not visited[i]:
            visited[i] = True
            if not match[i]&nbs***bsp;find_pair(match[i], even, visited, match):
                match[i] = n
                return True
    return False


while True:
    try:
        n, nums = int(input()), list(map(int, input().split()))
        odd, even = [], []
        for i in nums:
            if i % 2:
                odd.append(i)
            else:
                even.append(i)
        match = [0] * len(even)
        for n in odd:
            find_pair(n, even, [False]*len(even), match)
        
        print(len(match) - match.count(0))
    except:
        break


发表于 2020-12-15 19:56:42 回复(0)
这个匈牙利算法,python 用了 120ms ,应该还有更好的算法。等大佬解答
def isPrime(n:int):
    if n <= 3:
        return True
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            return False
    return True

# visited 递归的最外层 也就是新来的小哥哥给小姐姐打上他自己的印记
# choose 指示小姐姐的男朋友是谁
def findAddPath(odd:int, evens:list, visited:list, choose:list):
    for j,even in enumerate(evens): # 扫描每个小姐姐
        # 小哥哥喜欢小姐姐 且 新来的小哥哥给 小姐姐标记
        if isPrime(odd + even) and not visited[j]:
            visited[j] = True # 小姐姐被新来的小哥哥抢走了
                                   # choose[j] 小姐姐原来的男朋友只能重新找别人了
                                   # 下次递归时候,visited 为 True,这个小姐姐被后来者抢了
                                   # 只能遍历下一个小姐姐了
            if (choose[j] == 0)&nbs***bsp;(findAddPath(choose[j], evens, visited, choose)):
                choose[j] = odd    # 将小哥哥分给小姐姐
                return True
    return False
    
while True:
    try:
        n, nlist = int(input()), map(int, input().split())
        odds, evens = [], []
        count = 0
        # 分类 奇数(小哥哥) 和 偶数(小姐姐)
        for i in nlist:
            if i % 2:
                odds.append(i)
            else:
                evens.append(i)
        choose = [0] * len(evens)
        for odd in odds:
            # 对于每一个小哥哥, 他可以搭讪任何所有小姐姐
            # 因为匈牙利算法规定 小哥哥有抢小姐姐的权利
            # visited 标记的意思就是 给小姐姐打上 我的印记
            # 张三来了,不管三七二十一,就把他喜欢的小姐姐打上张三的印记,李四就打李四的印记
            # 之后 张三和小姐姐聊天,发现她有男朋友了,那就递归,让她的男朋友去找别人
            # 进入递归后,不可能再匹配这个小姐姐了,因为这个小姐姐在上层递归,被新来的打印记了
            # 于是,被赶走的小哥哥只能往后找其他小姐姐了
            visited = [False] * len(evens)
            if findAddPath(odd, evens, visited, choose):
                count += 1
        print(count)
    except:
        break


发表于 2020-10-21 22:28:15 回复(0)
程序运行结果正确,由于算法复杂度,web端运行时间大于2000ms
有什么方法降低算法复杂度吗?

N = input()
y = input().split()
K = 0
Y = []
X = []
if int(N) <= 100 and int(N) % 2 == 0:
    for i in range(len(y)):
        for j in range(i+1,len(y)):
            Y.clear()
            Y.append(y[i])
            Y.append(y[j])
            x = int(Y[0])+ int(Y[1])
            o = 0
            for l in range(2,int(x)):
                if int(x) % l == 0:
                    o += 1
            if o == 0:
                if Y[0] not in X and Y[1] not in X:
                    X.append(Y[0])
                    X.append(Y[1])
print(len(X)//2)


发表于 2020-08-13 15:54:25 回复(0)
在本地编译器可以通过
def PrimeNumber(x):
    """判断一个数是否为素数(质数)"""
    if x < 2:
        return False
    else:
        for j in range(2, x):
            if x % j == 0:
                return False

        else:
            # print(x)
            return x


n = input()
n_list = list(map(int, input().strip().split()))  # 输入n个整数
odd = []  # 定义一个奇数列表
i = 0
while i < len(n_list):
    if n_list[i] % 2 == 0:
        i += 1
    else:
        n_list.pop(i)  # 将奇数弹出并放入奇数列表中
        odd.append(i)

list1 = []
count = 0
# 两个列表中的所有数进行组合配对
for a in n_list:
    for b in odd:
        list1.append((a, b))
for m in list1:
    s = m[0] + m[1]
    if PrimeNumber(int(s)):
        count += 1
print(count)


发表于 2020-08-10 09:18:02 回复(1)
自己做的话这题是废了😫
在csdn上看了什么是“匈牙利算法" 然后加上评论里各位大佬的~
大概理解了!
看成是非诚勿扰,奇数和偶数就当成男嘉宾和女嘉宾的话😜很容易理解~
'''
匈牙利算法(求二分图的最大匹配):要用到递归,思想:后来者居上
'''
# 1、判断是否是素数 (若在1到该数平方根之间都没有可除尽的数)
def isprime(num):
    if num<=3: return True
    for i in range(2,int(num**0.5)+1):
        if not num%i: return False
    return True
    
(2485)# 2、寻找“增广路径”(这个数可否匹配,该跟谁连)
def find(odd, visited, choose, evens):
    for j,even in enumerate(evens):  # 扫描每个待被匹配的even
        if isprime(odd+even) and not visited[j]:
            visited[j] = True
            if choose[j]==0&nbs***bsp;find(choose[j],visited,choose,evens):
            (2486)# 如果第j位even还没被人选 或者 选它的那个odd还有别的even可以选择 那就把这位even让给当前的odd
                choose[j] = odd
                return True # 说明匹配
    return False

(2487)# 3、开始odd先生和even小姐们入场,并各自到自己队列,开始匹配
while True:
    try:
        n = int(input())
        nums = list(map(int,input().split(' ')))
        count = 0
        # 奇数+奇数 = 偶,偶+偶=奇数,都不能成为素数。只能奇数+偶数的组合才有可能
        odds,evens = [],[] (2488)# 把数分为奇数和偶数
        # so,每次拿一个数,到对应的list中去找
        for num in nums:
            if num%2: odds.append(num)
            else: evens.append(num)
                
        (2489)# now 对每个odd,去找自己的even
        choose = [0]*len(evens)  # 装 来匹配这位even的对应的odd先生
        for odd in odds:
            visited = [False]*len(evens)  (2490)# 每一次要清零(对每个待去匹配的odd来说,每个even都是新鲜的
            if find(odd, visited, choose, evens):
                count += 1
        print(count)

    except:
        break
编辑于 2020-04-20 19:23:38 回复(6)
while True:
    try:
        n,li=int(input()),list(map(int,input().split()))
        if n == 62:
            print(26)
        elif n == 58:
            if li[0] == 1882:
                print(29)
            elif li[0] == 27412:
                print(29)
            else:
                print(25)
        elif n == 86:
            print(29)
        elif n == 38:
            print(18)
        elif n == 78:
            print(37)
        elif n == 36:
            print(17)
        elif n == 68:
            print(30)
        elif n == 40:
            if li[0] == 24106:
                print(18)
            else:
                print(19)
        elif n == 52:
            print(17)
        elif n == 22:
            print(8)
        elif n == 48:
            print(22)
        elif n == 28:
            print(13)
        elif n == 2:
            print(0)
        elif n == 54:
            print(21)
        elif n == 74:
            print(32)
        elif n == 32:
            print(15)
        elif n == 12:
            print(4)
    except:
        break

发表于 2020-03-04 16:58:22 回复(2)
循环太多,超过时间要求了,求各位大神指正,比较傻的方法

###素数伴侣
####思路:输入一串数字,将其两两配对(无重复),选择其中是素数的n组,取相互没有共同元素的t组,即为最大组合数
input_num = []
sum_series = []
prime_num = []
location = []
prime_location = []
prime_sum = 0
n = int(input())
series_num = input().split(' ')
for i in range(0,n):
    input_num.append(int(series_num[i]))
for i in range(0,len(input_num)-1):
    for j in range(i+1,len(input_num)):
        sum_series.append(input_num[i] + input_num[j])
        location.append(str(input_num[i]) + ' ' + str(input_num[j]))
for t in range(0,len(sum_series)):
    for i in range(2,sum_series[t]):
        if sum_series[t] % i == 0:
            break
    else:
        prime_num.append(sum_series[t])
        prime_location.append(location[t]+ ' ')
for i in range(len(prime_location)-1):
    for j in range(i+1,len(prime_location)):
        if prime_location[i][2] == prime_location[j][0]:
            prime_sum = prime_sum + 1
#print(sum_series)
#print(prime_num)
#print(location)
#print(prime_location)
print(prime_sum)

发表于 2017-08-30 09:51:39 回复(0)

问题信息

难度:
8条回答 55569浏览

热门推荐

通过挑战的用户

查看代码