首页 > 试题广场 >

素数伴侣

[编程题]素数伴侣
  • 热度指数:178049 时间限制: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)
package main

import (
	"fmt"
)

func isPrime(n int) bool {
	if n < 2 {
		return false
	}

	for i := 2; i*i <= n; i++ {
		if n%i == 0 {
			return false
		}
	}
	return true
}

func getMax(nums []int) int {
	res := 0

	// 按奇偶划分
	odds := []int{}
	evens := []int{}
	for i := 0; i < len(nums); i++ {
		if nums[i]&1 == 1 {
			odds = append(odds, nums[i])
		} else {
			evens = append(evens, nums[i])
		}
	}

	n := len(evens)

	// 为每个奇数寻找匹配的偶数
	// 采用的策略:先到先得, 能让则让
	// 先到先得:如果该偶数的匹配为0,就直接与之匹配
	// 能让则让:如果该偶数已经匹配(匹配不为零),那么为与之匹配的奇数重新寻找匹配,如果能找到新的匹配,那么就把该偶数让给当前奇数
	// 否则该奇数不能找到匹配
	visited := make([]bool, n) //记录第i个偶数在本轮匹配中是否被访问过
	matched := make([]int, n)  //记录与第i个偶数匹配的奇数
	var match func(int) bool   //检查该奇数是否能找到合适的匹配, 注意:这是一个闭包函数
	match = func(odd int) bool {
		for i := 0; i < n; i++ {
			if isPrime(odd+evens[i]) && !visited[i] {
				visited[i] = true
				if matched[i] == 0 || match(matched[i]) {
					matched[i] = odd
					return true
				}
			}
		}
		return false
	}

	// 为每个奇数寻找匹配
	// 每轮的 visited 要清空
	for _, odd := range odds {
		visited = make([]bool, n)
		if match(odd) {
			res++
		}
	}

	return res
}

func main() {
	var n int
	fmt.Scanf("%d", &n)

	nums := make([]int, n)
	for i := 0; i < n; i++ {
		fmt.Scanf("%d", &nums[i])
	}
	fmt.Println(getMax(nums))
}

发表于 2022-08-17 09:56:11 回复(0)
GO语言实现 匈牙利算法

package main
import "fmt"
import "io"
import "math"

func main(){
    for {
        var n int
        var num int
        var odds []int
        var evens []int
        c, err := fmt.Scanf("%d\n", &n)
        if c==0 || err==io.EOF{
            break
        }
        for i:=0; i<n; i++{
            fmt.Scanf("%d", &num)
            if num % 2 == 0{
                evens = append(evens, num)
            }else{
                odds = append(odds, num)
            }
        }
        var suited map[int]int = make(map[int]int)
        var K int
        for i:=0; i<len(odds); i++{
            var visited map[int]int = make(map[int]int)
            ok := match(odds[i], evens, visited, suited)
            if ok {
                K++
            }
        }
        fmt.Println(K)
    }
}
func match(odd int, evens []int, visited map[int]int, suited map[int]int) bool {
    for _, even := range evens{
        if isPrime(odd + even) && visited[even] == 0 {
            visited[even] = 1
            if suited[even] == 0 || match(suited[even], evens, visited, suited) {
                suited[even] = odd
                return true
            }
        }
    }
    return false
}
func isPrime(num int) bool {
    n := math.Sqrt(float64(num))
    n2 := int(n + 0.5)
    for i := 2; i <= n2; i++{
        if num%i == 0{
            return false
        }
    }
    return true
}

发表于 2022-01-06 23:16:43 回复(0)
package main

import (
    "bufio"
    "fmt"
    "math/big"
    "os"
    "strconv"
    "strings"
)

func main() {


    scan := bufio.NewScanner(os.Stdin)
    for {
        scan.Scan()
        nums := scan.Text()
        if nums == "" {
            break
        }
        numsList := strings.Split(nums, " ")
        if len(numsList) == 1 {
            continue
        }
        numList := make([]int, 0, len(numsList))
        for _, v := range numsList {
            num, _ := strconv.Atoi(v)
            numList = append(numList, num)
        }
        ji, ou := GroupList(numList)
        result := 0
        match := make([]int, len(ou))
        for i:= 0; i < len(ji); i ++ {
            used := make([]int, len(ou))
            if find(ji[i], used, match, ou) {
                result ++
            }
        }
        fmt.Println(result)
    }

}

// 分奇偶
func GroupList(n []int) ([]int, []int) {
    odd := make([]int, 0, len(n))
    even := make([]int, 0, len(n))
    for _, v := range n {
        if v %2 ==  1 {
            odd = append(odd, v)
            continue
        }
        even = append(even, v)
    }
    return odd, even
}

func find(x int, l1, l2, l3 []int) bool {
    for i := 0; i < len(l3); i ++ {
        if big.NewInt(int64(l3[i] + x)).ProbablyPrime(0) && l1[i] == 0 {
            l1[i] = 1
            if l2[i] == 0 || find(l2[i], l1, l2, l3) {
                l2[i] = x
                return true
            }
        }
    }
    return false
}

发表于 2021-09-01 18:35:04 回复(0)