1. 程式人生 > 實用技巧 >題解 P5771【 [JSOI2016]反質數序列】

題解 P5771【 [JSOI2016]反質數序列】

題目連結

可以看出是道二分圖最大獨立集。

題目分析

分析可得:

  • 當兩個數奇偶性相同時,肯定滿足條件(和為偶數)

所以,源點連向偶數,奇數連向匯點(其實交換源匯點也可以,看心情叭)。如果兩數之和為質數,就在他們之間連邊。最後 $dinic$ 求最小割就 $over$ 啦。

注意點

  • 邊權均為 $1$ :所求答案為選擇的數量
  • 需要判斷 $1$ 出現的次數:兩個 $1$ 相加還是質數, 直接刪去一個 $1$ 就好了,不會影響結果。
  • 兩個數連邊時,注意源點和匯點的奇偶順序,提前判斷列舉的節點的奇偶正確性。

完整程式碼

#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
#define rt register int
#define int long long
const int N = 6010, M = 2e6,inf = 2e9,maxn = 2e5;
int n,m,S,T,tot = 1,a[N],head[N],cur[N],dep[N],f[M],prime[maxn],cnt;
bool vis[maxn + 10],tru[maxn + 10];
struct node {
	int to,nex;
}e[M];
inline void add(int x,int y,int w) {
	e[++tot] = (node) {y,head[x]}, f[tot] = w, head[x] = tot;
	e[++tot] = (node) {x,head[y]}, head[y] = tot;
}
inline void read(int &x) {
	x = 0;
	int ff = 1; char s = getchar();
	while(s < '0' || s > '9') {s = getchar();}
	while(s <= '9' && s >= '0') { x = x * 10 + s - '0'; s = getchar();}
	x *= ff;
}
inline bool bfs() {
	memset(dep,-1,sizeof(dep));
	dep[S] = 0, cur[S] = head[S];
	queue<int> q;
	q.push(S);
	int now,ver;
	while(!q.empty()) {
		now = q.front();
		q.pop();
		for(rt i = head[now]; i; i = e[i].nex) {
			ver = e[i].to;
			if(dep[ver] == -1 && f[i]) {
				dep[ver] = dep[now] + 1, cur[ver] = head[ver];
				if(ver == T) return 1;
				q.push(ver);
			}
		}
	} 
	return 0;
}
inline int find(int x,int limit) {
	if(x == T) return limit;
	int ver,flow = 0,tmp;
	for(rt i = cur[x]; i && flow < limit; i = e[i].nex) {
		cur[x] = i, ver = e[i].to;
		if(dep[ver] == dep[x] + 1 && f[i]) {
			tmp = find(ver,min(limit - flow,f[i]));
			if(!tmp) dep[ver] = -1;
			f[i] -= tmp, f[i ^ 1] += tmp, flow += tmp;
		}
	}
	return flow;
}
inline int dinic() {
	int res = 0,flow;
	while(bfs()) while(flow = find(S,inf)) res += flow;
	return res;
}
inline void init() {//預處理質數
	for(rt i = 2; i <= maxn; i ++) {
		if(!vis[i]) {
			prime[++cnt] = i;
			tru[i] = 1;
		}
		for(rt j = 1; j <= cnt && i *prime[j] <= maxn; j ++) {
			vis[i *prime[j]] = 1;
			if(i % prime[j] == 0) break;
		}
	}
} 
signed main() {
	init();
	read(n);
	S = n + 1, T = S + 1;
	int u,flag = 0,ans;
	for(rt i = 1; i <= n; i ++) {
		read(a[i]);
		if(a[i] == 1 && flag) {//如果出現2個及以上的1,直接刪除
			n --, i --;
			continue;
		}
		if(a[i] == 1) flag = 1;
		if(a[i] & 1) add(i,T,1);
		else add(S,i,1);
	}
	for(rt i = 1; i <= n; i ++) {
		if(a[i] & 1) continue; //與源點相連的應該是偶數,奇數跳過
		for(rt j = 1; j <= n; j ++) {
			if(a[j] % 2 == 0) continue;//參考上文
			if(tru[a[i] + a[j]]) add(i,j,1);
		}
	}
	ans = n;
	printf("%lld",ans - dinic());
	return 0;
}