刷到这个题目的时候想起了国庆项目的人群映射,基本类似,在这里再记录一下。
前言
若两个正整数的和为素数,则这两个正整数称之为“素数伴侣”,如2和5、6和13,它们能应用于通信加密。现在密码学会请你设计一个程序,从已有的N(N为偶数)个正整数中挑选出若干对组成“素数伴侣”,挑选方案多种多样,例如有4个正整数:2,5,6,13,如果将5和6分为一组中只能得到一组“素数伴侣”,而将2和5、6和13编组将得到两组“素数伴侣”,能组成“素数伴侣”最多的方案称为“最佳方案”,当然密码学会希望你寻找出“最佳方案”。输入:
有一个正偶数N(N≤100),表示待挑选的自然数的个数。后面给出具体的数字,范围为[2,30000]。
输出:
输出一个整数K,表示你求得的“最佳方案”组成“素数伴侣”的对数。
注意到,每个数的取值范围是[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;
}