HDU5750 Dertouzos
- 题目描述
A positive proper divisor is a positive divisor of a number n, excluding n itself. For example, 1, 2, and 3 are positive proper divisors of 6, but 6 itself is not.
Peter has two positive integers n and d. He would like to know the number of integers below n whose maximum positive proper divisor is d.
- Input
There are multiple test cases. The first line of input contains an integer T (1≤T≤106), indicating the number of test cases. For each test case:
The first line contains two integers n and d (2≤n,d≤109).
- Output
For each test case, output an integer denoting the answer.
- Sample Input
9
10 2
10 3
10 4
10 5
10 6
10 7
10 8
10 9
100 13
- Sample Output
1
2
1
0
0
0
0
0
4
- Tips
题意:
我们把n的不包括其本身的最大因子叫做最大真因子(positive proper divisor),即为PPD(n)。现有i < n,求满足PPD(i) = d的i的个数。
对于一个大于1的正整数d,它总能写成n个质数相乘的形式,即:
则对于所有大于1的素数x ≤ p1,有i = x * d,使PPD(i) = d。下面简要说明:
1° 若x为合数,则能找到x的一个不为1且不为其本身的因子k,设x’ = x / k,则i = x’ * (k * d),即PPD(i) ≥ k * d > d,与要求矛盾。
2° 若x为素数,但是x > p1,那么令d’ = d / p1,则有i = p1 * (x * d’ ),则有PPD(i) ≥ (x * d’ ) > d与要求矛盾。
综上,要求满足i < n且PPD(i) = d的数的个数,即要求满足大于1的素数x,且x ≤ p1的个数。
我们可以先用埃式筛法打出素数表(由于测试次数T较大,需要预处理素数表,直接判断素数会TLE)。略微分析一下,可以发现i/d小于1e5,只需打出1e5范围内的素数表就行了。
接着枚举所有素数,判断d mod x是否等于0,如果条件为真,则说明x = p1,计数器+1后结束枚举(在枚举x肯定大于p1了,不满足题意)。由于当x > d时,PPD(i) >= x > d不可能为d,当x * d ≥ n时i = x * d ≥ n不符合题意退出循环。
以上是题意分析,下面简单说明埃式筛法:
埃式筛法的核心一个素数的k(k为大于一的正整数)倍为非素数,将其筛去,这样最后留下的表就是一张素数表了。
误区:
刚开始拿到这道题时,走入了一个误区,认为只要枚举解区间(d, n)内的所有数i,则当n % d == 0 && n / d < d && n/d是素数就是一个满足题意的数。但是却一直TLE。究其原因可能是解区间(d,n)的长度可能非常大(达到1e9),而测试组数又较多,所以导致耗时较长,最后超时。而通过枚举素数,由于1e5范围内的素数个数最大也不过(1e5/2)是个很小的数,很快就能枚举完,所以不会TLE。
- Answer
#include<bits/stdc++.h>
using namespace std;
const int SIZE = 1e5 + 7;
int prime[SIZE];
bool vis[SIZE];
int cnt = 0;
void set_prime(){
memset(vis, true, sizeof(vis));
vis[0] = vis[1] = false;
for(int i = 2; i < SIZE; i++){
if(vis[i]){ //如果i是素数
prime[cnt++] = i;//加入素数表
for(int j = i + i; j < SIZE; j += i){//所有i的k(k>=2)被的数肯定不是素数,删掉
vis[j] = false;
}
}
}
return;
}
int main(void){
int t;
set_prime();//初始化素数表
scanf("%d", &t);
for(int k = 0; k < t; k++){
int n, d, ans = 0;
scanf("%d %d", &n, &d);
for(int i = 0; i < cnt; i++){//枚举x
if(prime[i] > d || prime[i] * d >= n) //若x > d,则这之后的PPD肯定不为d,或者,枚举处来的数比n大,则已经枚举结束
break;
if(d % prime[i] == 0){//prime[i]为d的最小素因子,若大于d的最小素因子,则d的最大真约束肯定不为d
ans++;
break;
}
ans++;
}
printf("%d\n", ans);
}
return 0;
}