2023NOIP A层联测17 黑暗料理

题目大意

给出一个长度为 n n n的序列 a i a_i ai,要求删去若干个数,使得剩下的数中任意两个数都不是质数。在满足条件的情况下最多能保留几个数。

T T T组数据。

1 ≤ T ≤ 4 , 1 ≤ n ≤ 750 , 1 ≤ a i ≤ 1 0 9 1\leq T\leq 4,1\leq n\leq 750,1\leq a_i\leq 10^9 1T4,1n750,1ai109

时间限制 2000 m s 2000ms 2000ms,空间限制 512 M B 512MB 512MB


题解

首先,如果一个数 k k k不是质数,那一定 k k k存在一个质因数 p p p满足 p ≤ k p\leq \sqrt k pk

那么,我们可以预处理出 5 × 1 0 4 5\times 10^4 5×104以内的所有质数,用这些质数即可判断 2 × 1 0 9 2\times 10^9 2×109以内的每个数是不是质数。若枚举每两个数来判断是不是质数,则时间复杂度是 O ( n 2 P ) O(n^2P) O(n2P)的(其中 P P P 5 × 1 0 4 5\times 10^4 5×104以内的质数个数, P = 5133 P=5133 P=5133)。

我们考虑优化。对于每个质数 p p p,令 b i = a i % p b_i=a_i\% p bi=ai%p,然后将 b i b_i bi排序,那么此时 b i b_i bi由若干个值相同的部分组成。假设有一部分的值都为 x x x,则我们需要找到值都为 p − x p-x px的部分,则枚举这两部分的 a a a值。设第一部分枚举到 a i a_i ai,第二部分枚举到 a j a_j aj,则如果 a i + a j > p a_i+a_j>p ai+aj>p,则 a i + a j a_i+a_j ai+aj一定是 p p p的若干倍,那么其一定是合数,做一个标记。

你可能会问:对于每个质数,最多有可能标记 n 2 n^2 n2次,那时间复杂度不还是 O ( n 2 P ) O(n^2P) O(n2P)吗?

对于每个 a i + a j a_i+a_j ai+aj,它只会被它的质因数打标记,而 a i + a j a_i+a_j ai+aj的质因数最多为 log ⁡ ( a i + a j ) \log(a_i+a_j) log(ai+aj)个,所以平摊下来,时间复杂度为 O ( n 2 log ⁡ v ) O(n^2\log v) O(n2logv),其中 v v v a i + a j a_i+a_j ai+aj的最大值。枚举的时间复杂度为 O ( n P ) O(nP) O(nP),所以这部分的时间复杂度为 O ( n 2 log ⁡ v + n P ) O(n^2\log v+nP) O(n2logv+nP)

当然,也可以用用 Miller-Rabin \text{Miller-Rabin} Miller-Rabin算法来判断 a i + a j a_i+a_j ai+aj是不是质数。

知道了每两个数之和是否为质数之后,我们建一个图,对于和为质数的两个数,我们将这两个数在图上对应的点连一条边,那么题意就是求最大点独立集。

我们观察图的性质。

如果组成的质数为偶质数,那么这个质数一定为 2 2 2,组成它的两个 a a a值一定都为 1 1 1。也就是说,在所有 a i a_i ai中只保留最多一个 1 1 1,即可保证不会出现偶质数。这个在输入的时候特殊处理一下即可。

那么, a i + a j a_i+a_j ai+aj为质数的话,只可能为奇质数,那么 a i a_i ai a j a_j aj的奇偶性一定不同。于是,上面的图就是一个二分图了。二分图的最大点独立集等于二分图的顶点数减去二分图的最大匹配数,那我们只需要求这个二分图的最大匹配即可。

这个二分图的点数为 n n n,边数为 m = n 2 m=n^2 m=n2。我们可以用匈牙利算法来求二分图的最大匹配,时间复杂度为 O ( n m ) O(nm) O(nm),即 O ( n 3 ) O(n^3) O(n3)。感觉会 TLE \text{TLE} TLE,但是匈牙利的时间复杂度为点数乘边数。设左边的点数为 x x x,则最坏情况下要做的次数为 n ⋅ x ( n − x ) n\cdot x(n-x) nx(nx),最大值为 n 3 4 \dfrac{n^3}{4} 4n3,而这是跑不满的。所以用匈牙利算法是可行的。

当然,也可以用 Dinic \text{Dinic} Dinic算法来求二分图的最大匹配,时间复杂度为 O ( n m ) O(n\sqrt m) O(nm ),即 O ( n 2 ) O(n^2) O(n2)

求完最大匹配之后,用点数减去最大匹配即可得到这个图的最大点独立集,也就是答案。

时间复杂度为 O ( ∑ ( n 2 log ⁡ v + n P + n 3 ) ) O(\sum(n^2\log v+nP+n^3)) O((n2logv+nP+n3)),其中 n 3 n^3 n3是带一个 1 4 \dfrac 14 41的常数的,而且这跑不满,时限还给了 2000 m s 2000ms 2000ms,所以这是可以过的。

code

#include
using namespace std;
const int N=750,P=50000;
int T,n,p1,p2,q1,q2,ans,a[N+5],p[P+5],z[P+5],gt[N+5],t[N+5][N+5];
int tot,d[2*N*N+5],l[2*N*N+5],r[N+5],cx[N+5],cy[N+5];
struct node{
	int x,id;
}b[N+5];
bool cmp(node ax,node bx){
	return ax.x<bx.x;
}
void add(int xx,int yy){
	l[++tot]=r[xx];d[tot]=yy;r[xx]=tot;
}
void init(){
	for(int i=2;i<=P;i++){
		if(!z[i]) p[++p[0]]=i;
		for(int j=1;j<=p[0]&&i*p[j]<=P;j++){
			z[i*p[j]]=1;
			if(i%p[j]==0) break;
		}
	}
}
int dfs(int u){
	for(int i=r[u];i;i=l[i]){
		if(gt[d[i]]) continue;
		gt[d[i]]=1;
		if(!cy[d[i]]||dfs(cy[d[i]])){
			cx[u]=d[i];cy[d[i]]=u;
			return 1;
		}
	}
	return 0;
}
int main()
{
//	freopen("cooking.in","r",stdin);
//	freopen("cooking.out","w",stdout);
	init();
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		tot=0;
		memset(r,0,sizeof(r));
		memset(t,0,sizeof(t));
		for(int i=1,bz=0;i<=n;i++){
			scanf("%d",&a[i]);
			if(a[i]==1){
				if(!bz) bz=1;
				else{
					--i;--n;continue;
				}
			}
			t[i][i]=1;
		}
		for(int nw=1;nw<=p[0];nw++){
			for(int i=1;i<=n;i++) b[i]=(node){a[i]%p[nw],i};
			sort(b+1,b+n+1,cmp);
			p1=p2=1;q1=q2=n;
			for(;p2<=n&&b[p2].x==0;p2++){
				for(int i=p1;i<=p2;i++){
					int w1=b[i].id,w2=b[p2].id;
					t[w1][w2]=t[w2][w1]=1;
				}
			}
			--p2;p1=p2+1;
			for(;;p1=p2+1,q1=q2-1){
				while(p1<=n&&q1>=1&&b[p1].x+b[q1].x!=p[nw]){
					if(b[p1].x+b[q1].x<p[nw]) ++p1;
					else --q1;
				}
				if(p1>n||q1<1||p1>q1) break;
				p2=p1;q2=q1;
				while(p2+1<=n&&b[p2+1].x==b[p1].x) ++p2;
				while(q2-1>=1&&b[q2-1].x==b[q1].x) --q2;
				for(int i=p1;i<=p2;i++){
					for(int j=q2;j<=q1;j++){
						int w1=b[i].id,w2=b[j].id;
						if(a[w1]+a[w2]>p[nw]) t[w1][w2]=t[w2][w1]=1;
					}
				}
			}
		}
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				if(!t[i][j]) add(i,j);
			}
		}
		ans=n;
		memset(cx,0,sizeof(cx));
		memset(cy,0,sizeof(cy));
		for(int i=1;i<=n;i++){
			if(a[i]%2==0) continue;
			if(!cx[i]){
				memset(gt,0,sizeof(gt));
				ans-=dfs(i);
			}
		}
		printf("%d\n",ans);
	}
	return 0;
}

你可能感兴趣的:(题解,好题,题解,c++,1024程序员节)