【2020年牛客暑假第九场】E题 Groundhog Chasing Death 质因子分解
传送门: https://ac.nowcoder.com/acm/contest/5674/E
题意
求
∏
i
=
a
b
∏
i
=
c
d
g
c
d
(
x
i
,
y
j
)
m
o
d
998244353
的
值
求\prod_{i=a}^b\prod_{i=c}^dgcd(x^i, y^j) mod998244353的值
求∏i=ab∏i=cdgcd(xi,yj)mod998244353的值
思路
观察上式,我们知道
g
c
d
gcd
gcd是
x
,
y
x,y
x,y的所有质因子个数的
m
i
n
(
a
,
b
)
min(a,b)
min(a,b),即
g
c
d
(
x
,
y
)
=
∑
i
=
1
n
p
i
m
i
n
(
a
i
,
b
i
)
(
a
i
,
b
i
分
别
是
x
,
y
公
共
质
因
子
的
次
幂
)
gcd(x,y)=\sum_{i=1}^np_i^{min(ai,bi)}(ai,bi分别是x,y公共质因子的次幂)
gcd(x,y)=i=1∑npimin(ai,bi)(ai,bi分别是x,y公共质因子的次幂)
所以,我们可以统计
x
,
y
x,y
x,y的每个公共质因子的次幂,然后再全部整合累乘得出答案。
一步一步分析:
要
求
∏
i
=
a
b
∏
i
=
c
d
g
c
d
(
x
i
,
y
j
)
要求\prod_{i=a}^b \prod_{i=c}^dgcd(x^i, y^j)
要求∏i=ab∏i=cdgcd(xi,yj)
其
实
求
的
就
是
∏
p
∣
x
,
p
∣
y
p
k
∑
i
=
a
b
∑
j
=
c
d
m
i
n
(
A
i
,
B
j
)
(
A
,
B
分
别
为
x
,
y
分
解
出
质
因
子
p
的
次
幂
)
其实求的就是\prod_{p|x ,p|y}p_k^{\sum_{i=a}^{b}\sum_{j=c}^dmin(Ai,Bj)}(A,B分别为x,y分解出质因子p的次幂)
其实求的就是∏p∣x,p∣ypk∑i=ab∑j=cdmin(Ai,Bj)(A,B分别为x,y分解出质因子p的次幂)
先
统
计
x
,
y
所
有
的
公
共
质
因
子
的
次
幂
,
然
后
再
累
乘
即
可
先统计x,y所有的公共质因子的次幂,然后再累乘即可
先统计x,y所有的公共质因子的次幂,然后再累乘即可
所
以
我
们
可
以
先
质
数
筛
把
1
e
9
以
内
的
所
有
质
数
筛
出
来
,
所
以
范
围
我
定
为
2
e
5
以
内
的
质
数
了
,
然
后
再
分
解
x
和
y
的
质
因
子
,
分
别
把
公
共
质
因
子
p
和
该
因
子
的
次
幂
c
x
,
c
y
存
在
数
组
里
,
方
便
下
面
步
骤
的
实
现
所以我们可以先质数筛把\sqrt{1e9}以内的所有质数筛出来,所以范围我定为2e5以内的质数了,然后再分解x和y的质因子,分别把公共质因子p和该因子的次幂cx,cy存在数组里,方便下面步骤的实现
所以我们可以先质数筛把1e9以内的所有质数筛出来,所以范围我定为2e5以内的质数了,然后再分解x和y的质因子,分别把公共质因子p和该因子的次幂cx,cy存在数组里,方便下面步骤的实现
然
后
单
独
分
析
单
个
公
共
质
因
子
p
,
得
p
∑
i
=
a
b
∑
j
=
c
d
m
i
n
(
A
i
,
B
j
)
然后单独分析单个公共质因子p,得p^{\sum_{i=a}^b\sum_{j=c}^d min(Ai,Bj)}
然后单独分析单个公共质因子p,得p∑i=ab∑j=cdmin(Ai,Bj)
先
比
较
A
i
和
B
j
的
大
小
:
先比较Ai和Bj的大小:
先比较Ai和Bj的大小:
当
A
i
≤
B
j
时
,
即
j
≥
⌈
A
i
B
⌉
,
p
m
i
n
(
A
i
,
B
j
)
=
p
A
i
\qquad当Ai \leq Bj时,即j \geq \left \lceil \frac{Ai}{B}\right \rceil,p^{min(Ai,Bj)}=p^{Ai}
当Ai≤Bj时,即j≥⌈BAi⌉,pmin(Ai,Bj)=pAi
当
A
i
>
B
j
时
,
即
j
<
⌈
A
i
B
⌉
,
p
m
i
n
(
A
i
,
B
j
)
=
p
B
j
\qquad当Ai > Bj时,即j < \left \lceil \frac{Ai}{B}\right \rceil,p^{min(Ai,Bj)}=p^{Bj}
当Ai>Bj时,即j<⌈BAi⌉,pmin(Ai,Bj)=pBj
接下来就有两种求解方式了,实质上是一种,只不过枚举顺序有点变化而已。
方法一:先枚举 i i i再枚举公共质因子
这个方法有点一小麻烦,理解有点困难。
因为先把
i
i
i确定下来之后,
j
j
j也就确定下来了,因为
j
=
⌈
A
i
B
⌉
j= \left \lceil \frac{Ai}{B}\right \rceil
j=⌈BAi⌉。
再来看这个式子:
∑
i
=
a
b
∑
j
=
c
d
m
i
n
(
A
i
,
B
j
)
\sum_{i=a}^{b}\sum_{j=c}^dmin(Ai,Bj)
∑i=ab∑j=cdmin(Ai,Bj)
因为枚举的是
i
i
i,并且
j
j
j已经确定了,再根据大小比较,所以上面的式子就是两种变化:
∑
i
=
a
b
∑
j
=
m
a
x
(
⌈
A
i
B
⌉
,
c
)
d
A
i
\sum_{i=a}^{b}\sum_{j=max(\left \lceil \frac{Ai}{B}\right \rceil,c)}^dAi
i=a∑bj=max(⌈BAi⌉,c)∑dAi
∑
i
=
a
b
∑
j
=
c
d
B
j
\sum_{i=a}^{b}\sum_{j=c}^dBj
i=a∑bj=c∑dBj
所以就有以下三种情况:
当
j
<
c
时
,
∑
i
=
a
b
∑
j
=
c
d
m
i
n
(
A
i
,
B
j
)
=
A
i
∗
(
d
+
c
−
1
)
\qquad 当j<c时,\sum_{i=a}^{b}\sum_{j=c}^dmin(Ai,Bj)=Ai*(d+c-1)
当j<c时,∑i=ab∑j=cdmin(Ai,Bj)=Ai∗(d+c−1)
当 j > d 时 , ∑ i = a b ∑ j = c d m i n ( A i , B j ) = B ∗ ( c + d ) ∗ ( d − c + 1 ) 2 \qquad 当j>d时,\sum_{i=a}^{b}\sum_{j=c}^dmin(Ai,Bj)=B*\frac{(c+d)*(d-c+1)}{2} 当j>d时,∑i=ab∑j=cdmin(Ai,Bj)=B∗2(c+d)∗(d−c+1)
当 c ≤ j ≤ d 时 , ∑ i = a b ∑ j = c d m i n ( A i , B j ) = A i ∗ ( d − j + 1 ) + B ∗ ( j + c − 1 ) ∗ ( j − c ) 2 \qquad 当c\leq j \leq d时,\sum_{i=a}^{b}\sum_{j=c}^dmin(Ai,Bj)=Ai*(d-j+1)+B*\frac{(j+c-1)*(j-c)}{2} 当c≤j≤d时,∑i=ab∑j=cdmin(Ai,Bj)=Ai∗(d−j+1)+B∗2(j+c−1)∗(j−c)
当把所有的公共质因子的总次幂求出来之后就可以进行累乘得出答案,再求总次幂的过程中可以用欧拉降幂优化一下, 并且模数 998244353 998244353 998244353正好是一个质数,所以 φ ( 998244353 ) = 998244353 − 1 \varphi(998244353)=998244353-1 φ(998244353)=998244353−1。
Code(286ms)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> pdd;
#define INF 0x7f7f7f
#define mem(a, b) memset(a , b , sizeof(a))
#define FOR(i, x, n) for(int i = x;i <= n; i++)
const ll mod = 998244353;
// const int maxn = 1e5 + 10;
// const double eps = 1e-6;
const int N = 2e5 + 10;
int prime[N];
bool is_prime[N];
int cnt = 0;
int px[N], cx[N], py[N], cy[N], mx, my; // 是x和y分解的质因子和质因子的次幂和质因子的个数
ll mi[N]; // 求解出来每个质因子的总次幂
struct node{
int cx, cy;
};
vector<node> v1; // x和y的公共质因子的个数
vector<int> v2; // x和y的公共质因子的值
void sieve() // 质数筛
{
mem(is_prime, true);
is_prime[0] = is_prime[1] = false;
for(int i = 2;i < N; i++) {
if(is_prime[i])
{
prime[++cnt] = i;
for(int j = 1;j <= cnt && i * prime[j] < N; j++) {
is_prime[i * prime[j]] = false;
if(i % prime[j] == 0)
break;
}
}
}
}
void Divide(ll n, int *p, int *c, int &m) // 分解质因子
{
m = 0;
for(int i = 2;i * i <= n; i++) {
if(n % i == 0) {
p[++m] = i;
while(n % i == 0) {
c[m]++;
n /= i;
}
}
}
if(n > 1) {
p[++m] = n;
c[m] = 1;
}
}
ll quick_pow(ll a, ll b)
{
ll ans = 1;
while(b) {
if(b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
void solve() {
sieve();
ll a, b, c, d, x, y;
scanf("%lld%lld%lld%lld%lld%lld",&a,&b,&c,&d,&x,&y);
Divide(x, px, cx, mx); // 分解x和y的质因子(值、个数)并存在数组里
Divide(y, py, cy, my);
int ppx = 1, ppy = 1;
while(ppx <= mx && ppy <= my) { // 寻找x和y的公共质因子并存在数组里
if(px[ppx] == py[ppy]) {
v1.push_back(node{cx[ppx], cy[ppy]});
v2.push_back(px[ppx]);
ppx++; ppy++;
}
else if(px[ppx] < py[ppy]) ppx++;
else ppy++;
}
for(int i = a;i <= b; i++) { // 枚举i
for(int k = 0;k < v1.size(); k++) { // 每个质因子的个数A和B
// int p = v2[k]; // 提取质因子
int ccx = v1[k].cx, ccy = v1[k].cy; // 提取质因子个数
int j = (ccx * i + ccy - 1) / ccy; // 本来是ccx*i/ccy,不过要保证向上取整,即+(B-1)/B就可以保证了,或者使用ceil()函数
if(j < c) {
ll t = 1ll * (d - c + 1);
mi[k] = (mi[k] + t * i * ccx) % (mod - 1);
}
else if(j > d) {
ll t = 1ll * (d + c) * (d - c + 1) / 2;
mi[k] = (mi[k] + t * ccy) % (mod - 1);
}
else {
ll t1 = 1ll * (d - j + 1);
ll t2 = 1ll * (j - c) * (c + j - 1) / 2;
mi[k] = (mi[k] + t1 * ccx * i) % (mod - 1);
mi[k] = (mi[k] + t2 * ccy) % (mod - 1);
}
}
}
ll ans = 1;
for(int i = 0;i < v1.size(); i++) {
if(mi[i] == 0) continue;
ans = ans * quick_pow(v2[i], mi[i]) % mod;
}
printf("%lld\n",ans);
}
signed main() {
ios_base::sync_with_stdio(false);
//cin.tie(nullptr);
//cout.tie(nullptr);
#ifdef FZT_ACM_LOCAL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
signed test_index_for_debug = 1;
char acm_local_for_debug = 0;
do {
if (acm_local_for_debug == '$') exit(0);
if (test_index_for_debug > 20)
throw runtime_error("Check the stdin!!!");
auto start_clock_for_debug = clock();
solve();
auto end_clock_for_debug = clock();
cout << "Test " << test_index_for_debug << " successful" << endl;
cerr << "Test " << test_index_for_debug++ << " Run Time: "
<< double(end_clock_for_debug - start_clock_for_debug) / CLOCKS_PER_SEC << "s" << endl;
cout << "--------------------------------------------------" << endl;
} while (cin >> acm_local_for_debug && cin.putback(acm_local_for_debug));
#else
solve();
#endif
return 0;
}
方法二:先枚举公共质因子再枚举 i i i和 j j j
这个方法稍微简单一点,便于理解。
根据下面两个两个大小关系,我们可以先通过枚举每个公共质因子,然后两个循环枚举
i
i
i和
j
j
j比大小,确定每个质因子的总次幂即可。
当
A
i
≤
B
j
时
,
即
j
≥
⌈
A
i
B
⌉
,
p
m
i
n
(
A
i
,
B
j
)
=
p
A
i
当Ai \leq Bj时,即j \geq \left \lceil \frac{Ai}{B}\right \rceil,p^{min(Ai,Bj)}=p^{Ai}
当Ai≤Bj时,即j≥⌈BAi⌉,pmin(Ai,Bj)=pAi
当
A
i
>
B
j
时
,
即
j
<
⌈
A
i
B
⌉
,
p
m
i
n
(
A
i
,
B
j
)
=
p
B
j
当Ai > Bj时,即j < \left \lceil \frac{Ai}{B}\right \rceil,p^{min(Ai,Bj)}=p^{Bj}
当Ai>Bj时,即j<⌈BAi⌉,pmin(Ai,Bj)=pBj
所以我们可以得到下面两个式子求解公共质因子
p
p
p的总次幂:
∑
i
=
a
b
∑
j
=
m
a
x
(
⌈
A
i
B
⌉
,
c
)
d
A
i
\sum_{i=a}^{b}\sum_{j=max(\left \lceil \frac{Ai}{B}\right \rceil,c)}^dAi
i=a∑bj=max(⌈BAi⌉,c)∑dAi
∑
j
=
c
d
∑
i
=
m
a
x
(
⌈
B
j
A
⌉
,
a
)
b
B
j
\sum_{j=c}^{d}\sum_{i=max(\left \lceil \frac{Bj}{A}\right \rceil,a)}^bBj
j=c∑di=max(⌈ABj⌉,a)∑bBj
当然,这里有几个小问题。
- j ≥ ⌈ A i B ⌉ j \geq \left \lceil \frac{Ai}{B}\right \rceil j≥⌈BAi⌉,如果这里没有正好整除的时候,那还行,因为我们需要的时向上取整,但是如果正好整除呢,那么我们需要 j + 1 j+1 j+1即 j = A i B + 1 j=\frac{Ai}{B}+1 j=BAi+1
- 举一个小栗子,枚举 i i i的时候,因为 j = m a x ( ⌈ A i B ⌉ , c ) j=max(\left \lceil \frac{Ai}{B}\right \rceil,c) j=max(⌈BAi⌉,c),要是 j > d j>d j>d,那么就不进行下面的操作,同理枚举 j j j。
每处理一个公共质因子,把他的总次幂求解出来之后,即可将他用快速幂并加到答案 a n s ans ans里,因为可以用到欧拉降幂,所以快速幂之前的 p o w 1 + p o w 2 pow1+pow2 pow1+pow2先 m o d mod mod就不是 998244353 998244353 998244353,而是 998244353 − 1 998244353-1 998244353−1,不然会出错的。
Code (691ms)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> pdd;
#define INF 0x7f7f7f
#define mem(a, b) memset(a , b , sizeof(a))
#define FOR(i, x, n) for(int i = x;i <= n; i++)
const ll mod = 998244353;
// const int maxn = 1e5 + 10;
// const double eps = 1e-6;
const int N = 2e5 + 10;
int prime[N];
bool is_prime[N];
int cnt = 0;
int px[N], cx[N], py[N], cy[N], mx, my; // 是x和y分解的质因子和质因子的次幂和质因子的个数
struct node{
int cx, cy;
};
vector<node> v1; // x和y的公共质因子的个数
vector<int> v2; // x和y的公共质因子的值
void sieve() // 质数筛
{
mem(is_prime, true);
is_prime[0] = is_prime[1] = false;
for(int i = 2;i < N; i++) {
if(is_prime[i])
{
prime[++cnt] = i;
for(int j = 1;j <= cnt && i * prime[j] < N; j++) {
is_prime[i * prime[j]] = false;
if(i % prime[j] == 0)
break;
}
}
}
}
void Divide(ll n, int *p, int *c, int &m) // 分解质因子
{
m = 0;
for(int i = 2;i * i <= n; i++) {
if(n % i == 0) {
p[++m] = i;
while(n % i == 0) {
c[m]++;
n /= i;
}
}
}
if(n > 1) {
p[++m] = n;
c[m] = 1;
}
}
ll quick_pow(ll a, ll b)
{
ll ans = 1;
while(b) {
if(b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
void solve() {
sieve();
ll a, b, c, d, x, y;
scanf("%lld%lld%lld%lld%lld%lld",&a,&b,&c,&d,&x,&y);
Divide(x, px, cx, mx); // 分解x和y的质因子(值、个数)并存在数组里
Divide(y, py, cy, my);
int ppx = 1, ppy = 1;
while(ppx <= mx && ppy <= my) { // 寻找x和y的公共质因子并存在数组里
if(px[ppx] == py[ppy]) {
v1.push_back(node{cx[ppx], cy[ppy]});
v2.push_back(px[ppx]);
ppx++; ppy++;
}
else if(px[ppx] < py[ppy]) ppx++;
else ppy++;
}
ll ans = 1;
for(int k = 0;k < v2.size(); k++) { // 枚举每个公共质因子
ll pow1 = 0, pow2 = 0; // 下面两个循环的次幂
for(ll i = a;i <= b; i++) { // 先枚举i
ll j;
if(v1[k].cx * i % v1[k].cy == 0)
j = max(v1[k].cx * i / v1[k].cy, c);
else
j = max(v1[k].cx * i / v1[k].cy + 1, c); // 向上取整并且没有整除
if(j <= d)
pow1 = (pow1 + i * (d - j + 1) % (mod - 1)) % (mod - 1); // 欧拉降幂
}
pow1 = pow1 * v1[k].cx % (mod - 1); // 不要忘了系数
for(ll j = c;j <= d; j++) { // 再枚举j
ll i = max(v1[k].cy * j / v1[k].cx + 1, a);// 因为上面枚举i的时候处理一次重复的情况,所以这里就不需要再考虑了
if(i <= b)
pow2 = (pow2 + j * (b - i + 1) % (mod - 1)) % (mod - 1); // 欧拉降幂
}
pow2 = pow2 * v1[k].cy % (mod - 1); // 不要忘了系数
ans = ans * quick_pow(v2[k], (pow1 + pow2) % (mod - 1)) % mod;
}
printf("%lld\n",ans);
}
signed main() {
ios_base::sync_with_stdio(false);
//cin.tie(nullptr);
//cout.tie(nullptr);
#ifdef FZT_ACM_LOCAL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
signed test_index_for_debug = 1;
char acm_local_for_debug = 0;
do {
if (acm_local_for_debug == '$') exit(0);
if (test_index_for_debug > 20)
throw runtime_error("Check the stdin!!!");
auto start_clock_for_debug = clock();
solve();
auto end_clock_for_debug = clock();
cout << "Test " << test_index_for_debug << " successful" << endl;
cerr << "Test " << test_index_for_debug++ << " Run Time: "
<< double(end_clock_for_debug - start_clock_for_debug) / CLOCKS_PER_SEC << "s" << endl;
cout << "--------------------------------------------------" << endl;
} while (cin >> acm_local_for_debug && cin.putback(acm_local_for_debug));
#else
solve();
#endif
return 0;
}
感谢赛中和赛后牌王的“热情”指导,牌王无敌!!!