Educational Codeforces Round 106 (Rated for Div. 2)D. The Number of Pairs
题目大意
给你三个正整数 c , d , k \displaystyle c,d,k c,d,k,求满足 c ∗ l c m ( a , b ) + d ∗ g c d ( a , b ) = x \displaystyle c*lcm(a,b)+d*gcd(a,b)=x c∗lcm(a,b)+d∗gcd(a,b)=x的 ( a , b ) \displaystyle(a,b) (a,b)对的数量。其中 c , d , k \displaystyle c,d,k c,d,k均小于 1 0 7 \displaystyle10^7 107。
简要分析
由于数据量较大,加之又有多组测试样例,这就限定了我们的时间复杂度必须小之又小,大概只能 O ( n ) \displaystyle O(n) O(n)。
具体分析
我们首先要对题目的式子进行处理,把它化简,变形到我们可以较为轻松的处理,或是看出它到底要让我们求什么。
首先我们注意到式子中有
l
c
m
(
a
,
b
)
\displaystyle lcm(a,b)
lcm(a,b)和
g
c
d
(
a
,
b
)
\displaystyle gcd(a,b)
gcd(a,b),我们知道这两者之间的联系,即
l
c
m
(
a
,
b
)
∗
g
c
d
(
a
,
b
)
=
a
∗
b
\displaystyle lcm(a,b)*gcd(a,b)=a*b
lcm(a,b)∗gcd(a,b)=a∗b,但如果直接把这个式子代进去,似乎也不是非常的美观。
我们不妨设
g
c
d
(
a
,
b
)
=
g
\displaystyle gcd(a,b) = g
gcd(a,b)=g,那么
a
=
A
∗
g
,
b
=
B
∗
g
\displaystyle a = A*g,b = B*g
a=A∗g,b=B∗g,其中
g
c
d
(
A
,
B
)
=
1
\displaystyle gcd(A,B) = 1
gcd(A,B)=1,这样便把
a
,
b
\displaystyle a,b
a,b与它们的公因数之间的桥梁给直观的搭建了起来,同样利用一开始的式子,我们就可以得到
l
c
m
(
a
,
b
)
=
A
∗
B
∗
g
\displaystyle lcm(a,b) = A*B*g
lcm(a,b)=A∗B∗g。这样我们代入题中的式子,便可以得到如下式子:
g
∗
(
A
∗
B
∗
c
−
d
)
=
x
\displaystyle g*(A*B*c-d) = x
g∗(A∗B∗c−d)=x
考虑到左边的式子中含有因子
g
\displaystyle g
g,我们将其移到右侧,考虑到两侧均为整数便可得到
g
∣
x
\displaystyle g|x
g∣x。
对式子变形,得到:
A
∗
B
∗
c
=
x
g
+
d
\displaystyle A*B*c = \frac{x}{g}+d
A∗B∗c=gx+d
同样,跟刚才一样,注意到左边含有因子
c
\displaystyle c
c,说明
c
∣
(
x
g
+
d
)
\displaystyle c| (\frac{x}{g}+d)
c∣(gx+d),我们将
c
\displaystyle c
c移动到左边,得到:
A
∗
B
=
(
x
g
+
d
)
c
\displaystyle A*B = \frac{(\frac{x}{g}+d)}{c}
A∗B=c(gx+d)
对于每一个给定的
g
\displaystyle g
g式子右侧是一个定值,当然是满足条件的值,这样问题就转化为对于每一个给定的,满足条件的
g
\displaystyle g
g有多少组
(
A
,
B
)
\displaystyle (A,B)
(A,B)满足条件,由于
A
,
B
\displaystyle A,B
A,B互质,那么对于每一个式子右侧的质因子,左侧一定只有
A
或
B
\displaystyle A或B
A或B有,那么问题就在一次转化,转化为对所有的右侧质因子,逐一分配给
A
或
B
\displaystyle A或B
A或B ,问分配情况总数,这其实就是一个子集问题,设质因子个数为
n
\displaystyle n
n,则子集数为
2
n
\displaystyle 2^n
2n,也就是总的情况数。
具体实现细节
在分析完后,我们可以清楚的知道,我们只需要枚举每一个
x
\displaystyle x
x的的因子,判断它是否满足式子的条件(即上面分析过程中的几个整除条件),满足,则计算它对答案的贡献。由于数据量之大,意味着我们需要尽可能快的处理每一组数据,这时候我们就需要进行预处理。
分析,我们在处理每组数据时候的复杂度最快是
O
(
x
)
\displaystyle O(\sqrt{x})
O(x),所以我们需要
O
(
n
)
\displaystyle O(n)
O(n)的预处理或是接近这个速度的预处理。
对于每一个可能的数,我们需要预处理出它的质因子种类的个数,我们很容易联想到应用筛法,其中常见的两个筛法中较快的是欧拉筛,我们可以通过欧拉筛记录每个数的最小质因子,因为它在筛的过程中就是以每个数的最小质因子来筛的,所以很好联想。
但是我们又要怎么处理质因子个数呢,这其中似乎有着类似递推一样的存在,假设我们已经知道了比一个数小的所有数的质因子个数,又知道了它的最小质因子,那么我们思考,如果这个数除去最小质因子后的数中包含原来的最小质因子,那么它的质因子个数不就与除去后的数相同了吗,反之,如果不包含,个数则加一。
具体代码如下:
//#include<bits/stdc++.h> ----万能头----
#include <iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<cstring>
#include<cmath>
#include<queue>
#include<list>
#include<map>
#include<unordered_map>
using namespace std;
const int p = 1e9+7;
const int N = 2e7+5;
const int maxn = 1e5+10;
const long long INF = 1e17;
#define REP(i,n) for(int i = 1; i <= n; ++i)
#define REPA(i,j,n) for(int i = j; i <= n; ++i)
#define RREP(i,n) for(int i = n; i >= 1; --i)
#define REPR(i,j,n) for(int i = n; i >= j; --i)
typedef long long ll;
typedef pair<ll,ll> PII;
ll n,m,t;
ll k;
ll mind[N], val[N];
vector<ll> prime;
void get_pr(){
mind[1] = 1;//下面求质数个数的时候会用到,先不用管
for(int i = 2; i <= N; ++i){
if(!mind[i]){
mind[i] = i;//质数的最小质因子为其本身
prime.emplace_back(i);//存放质数
}
for(auto j : prime){//遍历之前获得每个质数
if(j*i >= N || j > mind[i])break;//如果越界,或是当前数的质因子小于遍历的质数时跳出
mind[i*j] = j;//记录最小质因子
}
}
for(int i = 2;i <= N; ++i)//获取每个数的质因数种类的个数
{
int j = i/mind[i];//获取除去当前数除以最小质因子之后的数,注意mind的初始化,第一个值!!!
val[i] = val[j]+(mind[i]!=mind[i/mind[i]]);//如果除去最小质因子后的数最小质因子还是当前数,则数量不变,反之加1
}
}
void solve() {
ll c,d,x;
scanf("%lld%lld%lld",&c,&d,&x);
ll ans = 0;
for(int i = 1; i * i<= x; ++i){
if(x % i != 0)continue;//如果i不是x的因子,则跳过
int k1 = x/i + d;//i为因子的情况,但注意还有一个对称的因子
if(k1 % c == 0)ans += 1<<val[k1/c];
if(i * i == x)continue;//注意,如果当前因子恰好是x的平方根,我们则不能重复计算
int k2 = i + d;//x去除以i的对称因子,即是i本身
if(k2 % c == 0)ans += 1<<val[k2/c];
}
cout<<ans<<'\n';//这个每组测试的复杂度就成了根号x
}
int main() {
//ios::sync_with_stdio(0);
//cin.tie(0);
//cout.tie(0);
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
get_pr();//init,precalculate
scanf("%lld",&t);
while(t--)
solve();
fclose(stdin);
fclose(stdout);
}