Codeforces 1047C (線性篩+因數分解)

題面

傳送門

分析

1.暴力做法
首先先把每個數除以gcd(a1,a2,an)gcd(a_1,a_2 \dots,a_n )
可以O(namax)O(n\sqrt {a_{max}})的時間內分解出所有數的質因數,然後統計出現次數最多的質因數,設最多出現次數爲xx,然後把其他的數去掉就可以了,答案爲nxn-x
例:
n=4,a={6,9,15,30}n=4,a=\{6,9,15,30\}
處理後a={2,3,5,10}a=\{2,3,5,10\}
2=22=2
3=33=3
5=55=5
10=2×510=2 \times 5
我們可以看出質因數2出現了2次,3出現了1次,5出現了2次
出現次數最多的質因數爲2或5,均出現了2次
故答案爲4-2=2

2.優化
可以看出算法的瓶頸在質因數分解,我們考慮如何優化質因數分解算法
這是一般的質因數分解算法

set<int>S;
for(int i=2; i<=x; i++) {
   while(x%i==0 &&x != i) {
   	n/=i;
   	S.push(i);
   }
   if(x == i) {
   	S.push(i)
   	break;
   }
}

該算法的時間複雜度爲O(x)O(\sqrt x)
問題在於該算法需要枚舉能整除x的數,效率比較低,如果對於每個數x,我們知道能被整除x的最小質數minprime[x],算法的效率就可以提高了
那麼如何求出minprime呢?
我們想到線性篩法的過程

for(int i=2; i<=n; i++) {
		if(!vis[i]) {
			prime[++k]=i;
		}
		for(ll j=1; j<=k&&(ll)i*prime[j]<=n; j++) {
		    minprime[(ll)i*prime[j]]=prime[j];
			vis[(ll)i*prime[j]]=1;
			if(!(i%prime[j])) break;
		}
	}

其中的每個合數只會被篩一次,而prime[j]不正好是能整除i×prime[j]i\times prime[j]的最小質數嗎?
因此可以把線性篩改成這個樣子

for(int i=2; i<=n; i++) {
		if(!vis[i]) {
			minprime[i]=i;
			prime[++k]=i;
		}
		for(ll j=1; j<=k&&(ll)i*prime[j]<=n; j++) {
			minprime[(ll)i*prime[j]]=prime[j];
			vis[(ll)i*prime[j]]=1;
			if(!(i%prime[j])) break;
		}
	}

我們就求出了minprime,注意若p是質數,minprime[p]=p
然後可以寫出分解質因數的算法


	if(!vis[x]||x==1) {//如果是1或質數,直接返回
		cnt[x]++;
		return;
	}
	while(x>1) {
		int t=minprime[x];
		cnt[t]++;
		while(x%t==0&&x!=1) {
			x/=t;
		}

}

因爲每次循環x至少要除以2,所以時間複雜度爲O(log2x)O(log_2 x)

代碼

#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 300005
#define maxv 15000005
using namespace std;
typedef long long ll;
inline int gcd(int a,int b) {
	return b==0?a:gcd(b,a%b);
}
int n;
int a[maxn];
int vis[maxv];
int minprime[maxv];
int cnt[maxv];
int prime[maxv];
int k=0;

void sieve(int n) {
	for(int i=2; i<=n; i++) {
		if(!vis[i]) {
			minprime[i]=i;
			prime[++k]=i;
		}
		for(ll j=1; j<=k&&(ll)i*prime[j]<=n; j++) {
			minprime[(ll)i*prime[j]]=prime[j];
			vis[(ll)i*prime[j]]=1;
			if(!(i%prime[j])) break;
		}
	}
}

void div(int x) {
	if(!vis[x]||x==1) {
		cnt[x]++;
		return;
	}
	while(x>1) {
		int t=minprime[x];
		cnt[t]++;
		while(x%t==0&&x!=1) {
			x/=t;
		}
	}
}
int main() {
	scanf("%d",&n);
	sieve(15000000);
	int g=0;
	for(int i=1; i<=n; i++) {
		scanf("%d",&a[i]);
		g=gcd(g,a[i]);
	}
	for(int i=1; i<=n; i++) {
		a[i]/=g;
	}
	for(int i=1; i<=n; i++) {
		div(a[i]);
	}
	int ans=0;
	for(int i=2; i<=15000000; i++) {
		ians=max(ans,cnt[i]);
	}
	if(ans==0) printf("-1\n");
	else printf("%d\n",n-ans);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章