題目
思路
我們可以 的找到每一組不能共存的數字,問題轉化爲圖的最大獨立集。
衆所周知,最大獨立集 最小覆蓋集 全集,問題轉化爲求最小覆蓋集。 選最少的點,使得任何一條邊都存在一個端點被選中。
這張圖是否有什麼性質呢?將所有數字 提出來單獨考慮,那麼數字 ,兩個數字求和 。若二者相加爲質數,則一定是奇質數,即:有連邊的兩個點,奇偶性不同。
所以我們按照奇偶性將點分類,得到了一張 二分圖。實際連邊的時候無需考慮分類,此處僅爲了證明該圖爲二分圖罷了。
二分圖中,最小覆蓋集爲最大匹配數量。證明比較簡單。
- 首先證明最小覆蓋集 最大匹配數。如果一個匹配的兩個端點都需要被選入最小覆蓋集,就存在一條增廣路徑。
- 其次證明最小覆蓋集 最大匹配數。因爲匹配中的每一條邊都需要至少一個點來覆蓋。
現在我們要做的只是求最大匹配。匈牙利算法可好用了! 使用 算法進行求解,複雜度是 的。這是很粗略的上界。希望有人能夠證明出,這個上界取不到,或者取的到!
複雜度證明?這篇文章告訴你,仿照 的算法分析的複雜度:
- 增廣一次是 的,因爲最多每一條邊被訪問一次(退流 增廣)。
- 對於增廣路的長度 的情況,總複雜度 ,因爲增廣路長度嚴格遞增,最多 次。接下來考慮長度 的。
- 考慮最大匹配與當前匹配的不同之處,可以拆成很多增廣路徑。但一個點最多作爲兩條邊的端點:一個退流(取消已有匹配),一個增廣(連接最終匹配)。所以“網絡的差”的邊數是 的。而長度 ,故最多還有 條路徑,所以複雜度 的。
然後就做完了。只要你別忘了把 也討論一下(選擇 以後,將與 相連的點刪除,再求最大獨立集)。
代碼
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long int_;
inline int readint(){
int a; scanf("%d",&a); return a;
}
const int MaxN = 3002;
struct Edge{
int to, nxt, val;
Edge(int T=0,int N=0){
to = T, nxt = N, val = 1;
}
} edge[MaxN*MaxN];
int head[MaxN], cntEdge;
void addEdge(int a,int b){
edge[cntEdge] = Edge(b,head[a]);
head[a] = cntEdge ++;
}
int q[MaxN], d[MaxN], cur[MaxN];
bool bfs(int s,int t){
int L = 0, R = 0;
for(int i=s; i<=t; ++i)
d[i] = -1;
d[s] = 0; q[++ R] = s;
while(L != R){
int x = q[++ L];
for(int i=head[x]; ~i; i=edge[i].nxt)
if(d[edge[i].to] == -1)
if(edge[i].val > 0){
d[edge[i].to] = d[x]+1;
q[++ R] = edge[i].to;
}
}
return d[t] != -1;
}
inline int_ dfs(int x,int_ inFlow,int &T){
if(x == T) return inFlow;
int_ sum = 0, delta;
for(int &i=cur[x]; ~i; i=edge[i].nxt) /* 當前弧 */
if(d[edge[i].to] == d[x]+1)
if(edge[i].val > 0){
delta = min(inFlow-sum,1ll*edge[i].val);
delta = dfs(edge[i].to,delta,T);
edge[i].val -= delta;
edge[i^1].val += delta;
if((sum += delta) == inFlow) break;
}
if(sum != inFlow) d[x] = -1; return sum;
}
const int_ infty = (1ll<<61)-1;
int_ dinic(int s,int t){
int_ maxFlow = 0;
while(bfs(s,t)){
for(int i=s; i<=t; ++i)
cur[i] = head[i];
maxFlow += dfs(s,infty,t);
}
return maxFlow;
}
bool isPrime[MaxN*MaxN];
vector< int > primes;
void sievePrime(int n){
for(int i=2; i<=n; ++i)
isPrime[i] = true;
for(int i=2,len=0; i<=n; ++i){
if(isPrime[i]) ++ len,
primes.push_back(i);
for(int j=0; j<len; ++j){
if(i > n/primes[j]) break;
isPrime[i*primes[j]] = 0;
if(i%primes[j] == 0) break;
}
}
}
int a[MaxN];
int maxMatch(int n){
cntEdge = 0;
for(int i=0; i<=n+1; ++i)
head[i] = -1;
for(int i=1; i<=n; ++i) if(a[i]&1)
for(int j=i+1; j<=n; ++j)
if(isPrime[a[i]+a[j]])
addEdge(i,j);
for(int i=1; i<=n; ++i)
if(a[i]&1) addEdge(0,i);
else addEdge(i,n+1);
return dinic(0,n+1);
}
int main(){
int n = readint(), cnt = 0;
for(int i=1; i<=n; ++i)
a[i] = readint();
sort(a+1,a+n+1);
for(int i=1; i<=n&&a[i]==1; ++i)
swap(a[i],a[n+1-i]), ++ cnt;
int ans = n-cnt-maxMatch(n-cnt);
for(int i=(cnt=1); i<=n&&a[i]!=1; ++i)
if(!isPrime[a[i]+1]) a[cnt++] = a[i];
ans = max(ans,cnt-maxMatch(cnt));
printf("%d\n",ans);
return 0;
}