[JSOI2016]反質數序列

題目

傳送門 to luogu

思路

我們可以 O(n2)\mathcal O(n^2) 的找到每一組不能共存的數字,問題轉化爲圖的最大獨立集。

衆所周知,最大獨立集 ++ 最小覆蓋集 == 全集,問題轉化爲求最小覆蓋集。i.e.i.e. 選最少的點,使得任何一條邊都存在一個端點被選中。

這張圖是否有什麼性質呢?將所有數字 11 提出來單獨考慮,那麼數字 2\ge 2 ,兩個數字求和 4\ge 4 。若二者相加爲質數,則一定是奇質數,即:有連邊的兩個點,奇偶性不同

所以我們按照奇偶性將點分類,得到了一張 二分圖。實際連邊的時候無需考慮分類,此處僅爲了證明該圖爲二分圖罷了。

二分圖中,最小覆蓋集爲最大匹配數量。證明比較簡單。

  • 首先證明最小覆蓋集 \le 最大匹配數。如果一個匹配的兩個端點都需要被選入最小覆蓋集,就存在一條增廣路徑。
  • 其次證明最小覆蓋集 \ge 最大匹配數。因爲匹配中的每一條邊都需要至少一個點來覆蓋。

現在我們要做的只是求最大匹配。匈牙利算法可好用了! 使用 dinicdinic 算法進行求解,複雜度是 O(mn)\mathcal O(m\sqrt{n}) 的。這是很粗略的上界。希望有人能夠證明出,這個上界取不到,或者取的到!

複雜度證明?這篇文章告訴你,仿照 dinicdinic 的算法分析的複雜度:

  • 增廣一次是 O(m)\mathcal O(m) 的,因爲最多每一條邊被訪問一次(退流 oror 增廣)。
  • 對於增廣路的長度 n\le \sqrt{n} 的情況,總複雜度 O(mn)\mathcal O(m\sqrt{n}) ,因爲增廣路長度嚴格遞增,最多 n\sqrt{n} 次。接下來考慮長度 >n>\sqrt{n} 的。
  • 考慮最大匹配與當前匹配的不同之處,可以拆成很多增廣路徑。但一個點最多作爲兩條邊的端點:一個退流(取消已有匹配),一個增廣(連接最終匹配)。所以“網絡的差”的邊數是 O(n)\mathcal O(n) 的。而長度 >n>\sqrt{n} ,故最多還有 n\sqrt{n} 條路徑,所以複雜度 O(mn)\mathcal O(m\sqrt{n}) 的。

然後就做完了。只要你別忘了把 11 也討論一下(選擇 11 以後,將與 11 相連的點刪除,再求最大獨立集)。

代碼

#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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章