0%

欧拉筛!

欧拉筛

于CF1080_E题遇到对欧拉筛的活用,从新总结用法及原理

欧拉筛

欧拉筛/线性筛:核心是利用一个质数筛掉其他合数

动态规划的思想

任何一个合数C,能且只能被分解为唯一的 $C=i \cdot p_{min}$ ,其中 $p_{min}$ 是C的最小质因数,i是某个确定的整数。

算法外层循环遍历所有整数i,内层循环从小到大遍历已知质数集合p。我们把 $i \cdot p_{min}$ 当作已知的合数,把他标记为被筛出。

外层遍历i的过程中,若i是合数,那么其一定可以被分解为比自己小的倍数$i_{last}$与比他小的素数的积(比他小保证了在遍历到他之前已经被处理过),其会在处理i之前被处理为is_prime[$p_{min}*i_{last}$]=1;反之则说明其不是合数,我们直接将其添加到is_prime中即可

最关键的逻辑: if(i%p==0) break;

当i%p==0成立时,意味着质数p已经是整数i的质因数之一,且由于我们从小到大遍历质数,p就是i的最小质因数。此时我们不难考虑到,此时如果再进行合数的计算,就使得 $p_{next} \cdot i$ 的最小质因数不再是 $p_{next}$ 而是p,不满足我们在前面设定的最小质因数的合数条件,故break,等待后面的遍历来计算,防止重复。

欧拉筛模板代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
vector<int> primes;
bool is_prime[1000005];

void count_primes(){
for(int i=2;i<=1000005;i++){
if(!is_prime[i])
primes.push_back(i);
for(auto p:primes){
if(i*p>1000005) break;
is_prime[i*p]=1;
if(i%p==0) break;
}
}
}
primes存所有质数,is_prime用位置的值反应是否是素数,0是素数,1是合数。

E. Divisive Battle

对本题而言,题目核心及解出数列内的数的最小素因数和最大素因数,关键在于理解,若当前数的不同的素因数个数大于1,则Alice必定胜利(分解后内部有序,故需判断两个相邻的数分解后是否保持有序)

对上述计量方法插入对最大最小素因数的统计,及统计其个数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
int max_p[1000005];
vector<int> primes;
bool is_prime[1000005];
int min_p[1000005];

inline int gcd(int x,int y){
while(y){int t=x%y;x=y;y=t;}return x;
}

int cnt[1000005];//某个数的质数个数
void count_primes(){
min_p[1]=1;
max_p[1]=1;
cnt[1]=0;
for(int i=2;i<=1000000;i++){
if(!is_prime[i]){
primes.push_back(i);
min_p[i]=i;
max_p[i]=i;
cnt[i]=1;
}
for(auto p:primes){
if(i*p>1000000) break;
is_prime[i*p]=1;
min_p[i*p]=p;
max_p[i*p]=max(max_p[i],p);
if(i%p==0){
cnt[i*p]=cnt[i];//可以不写,未来还会来处理
break;
}else{
cnt[i*p]=cnt[i]+1;
}
}
}
}

cnt[i]的计算是两种状态转移
A.当i%p==0,可以不做处理,当前产生的合数无法增加素因数个数
B.当i%p!=0,此时合数在i的素因数基础上再+p,及素因数个数+1

当我们先手一次性处理完之后,即可直接判断

1
2
3
4
5
6
7
8
9
10
11
if(cnt[a[i]]>1){
cout<<"Alice"<<endl;
return;
}
//若某个数素因数个数多于1,直接判定Alice胜利
if(i<n && max_p[a[i]]>min_p[a[i+1]]){
cout<<"Alice"<<endl;
return;
}
//若在拆分过程中发现相邻两个数不满足拆分后素数last_max<=next_min,即不满足非递减,则Alice胜利
//除此之外,Bob胜利

重庆