E Groundhog Chasing Death(2020牛客暑期多校训练营(第九场))(思维+费马小定理+质因子分解)
链接:https://ac.nowcoder.com/acm/contest/5674/E
来源:牛客网
时间限制:C/C++ 2秒,其他语言4秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
题目描述
As we all know,“Groundhog chasing death” means “GCD”,while “GCD” stands for “greatest common divisor”.
So you need to calculate ∏ i = a b ∏ j = c d gcd ( x i , y j ) \displaystyle\prod_{i=a}^b\prod_{j=c}^d\gcd(x^i,y^j) i=a∏bj=c∏dgcd(xi,yj) modulo 998244353 {998244353} 998244353.
输入描述:
One line which contains six intergers a , b , c , d , x , y {a,b,c,d,x,y} a,b,c,d,x,y.
输出描述:
One line which contains
∏
i
=
a
b
∏
j
=
c
d
gcd
(
x
i
,
y
j
)
\displaystyle\prod_{i=a}^b\prod_{j=c}^d\gcd(x^i,y^j)
i=a∏bj=c∏dgcd(xi,yj) modulo
998244353
{998244353}
998244353.
示例1
输入
1 2 1 2 8 4
输出
2048
示例2
输入
1 2 3 4 120 180
输出
235140177
备注:
0 ⩽ a , b , c , d ⩽ 3 × 1 0 6 , 0 < x , y ⩽ 1 0 9 , a ⩽ b , c ⩽ d 0\leqslant{a,b,c,d\leqslant 3\times10^6,0<x,y\leqslant10^9},a\leqslant b,c\leqslant d 0⩽a,b,c,d⩽3×106,0<x,y⩽109,a⩽b,c⩽d.
题意
给出 a , b , c , d , x , y a,b,c,d,x,y a,b,c,d,x,y,让求出 ∏ i = a b ∏ j = c d gcd ( x i , y j ) \displaystyle\prod_{i=a}^b\prod_{j=c}^d\gcd(x^i,y^j) i=a∏bj=c∏dgcd(xi,yj)。
题解
暴力做法:枚举 i i i和 j j j,算出 x i x^i xi和 y j y^j yj求乘积。注意求幂的时候不能取模,因为取模后最大公约数就变了。时间复杂度 O ( n 2 l o g n ) O(n^2logn) O(n2logn),既会爆long long,又会超时。
改进做法:考虑 x x x和 y y y的公共素因子,取 i i i次方和 j j j次方就相当于把素因子的个数变为 i i i倍或 j j j倍。这样的话每次枚举时更新因子个数,枚举结束后再把各个素因子乘起来,乘的时候可以取模。时间复杂度 O ( n 2 l o g n ) O(n^2logn) O(n2logn)。会超时但不会爆long long了。
继续改进:考虑到枚举过程中 x x x和 y y y的值是不变的,所以预处理出 x x x和 y y y的公共素因子,并求出每个素因子分别在 x x x和 y y y中出现的次数,那么 x i x^i xi就相当于把x中的素因子个数都变成 i i i倍, y j y^j yj同理。
把 x x x和 y y y中每个素因子的个数分开考虑,因为题目要求的是求所有gcd的乘积,所以我们把用于构建gcd的素因子的个数直接加起来即可。
发现每枚举一个 i i i,对应的连续的 j j j存在线性关系:当 j j j比较小的时候, x i x^i xi和 y j y^j yj的最大公约数的限制主要体现在 y y y的素因子个数上。 j j j不断增加, y y y中的素因子个数就变成了一个个等差数列。可以利用等差数列求和公式 O ( 1 ) O(1) O(1)算出这一段区间的 j j j对 y y y的素因子个数的贡献,而当 y y y比较大时,gcd的限制主要体现在 x x x的素因子个数上,那么不论 j j j再怎么增加, i i i不变,那么构建出来的gcd的值就不会变,此时后一半区间的 y y y对素因子的贡献就是 x x x中素因子出现的个数乘以这一段 y y y的区间长度。
注意枚举的时候不要直接计算出素因子的幂,不然会超时。先保存每个素因子的总个数,之后再求幂和乘积。注意直接保存素因子的总个数会爆long long。需要利用费马小定理: a ( p − 1 ) ≡ 1 ( m o d p ) a^{(p-1)}≡1(mod\ p) a(p−1)≡1(mod p),把素因子的个数对“mod-1”取模即可。时间复杂度 O ( n l o g n ) O(n\ log n) O(n logn)
最后利用快速幂算出每个幂的值,然后求乘积即可。
代码
#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define _for(i, a) for(register int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for(register int i = (a), lennn = (b); i <= lennn; ++i)
#define scl(x) scanf("%lld", &x)
using namespace std;
typedef long long LL;
const int mod = 998244353;
int debug = 0;
LL a, b, c, d, x, y;
vector<LL> gcdd, gnumx, gnumy, mi; //gcdd保存x和y的公共素因子,gnumx保存x中每个公共素因子的个数,gnumy保存y中每个公共素因子的个数,mi保存最终答案中每个公共素因子的个数。
inline void init() {
gcdd.clear();
gnumx.clear();
gnumy.clear();
mi.clear();
}
struct Prime {
vector<int> arr;
int vis[100006];
void doit(int maxnum) {
for(int i = 2; i <= maxnum; ++i) {
if(!vis[i]) arr.push_back(i);
for(int j = 0; j < arr.size() && arr[j] * i <= maxnum; ++j) {
vis[arr[j] * i] = 1;
if(i % arr[j] == 0) break;
}
}
}
}P;
inline LL quickPow(LL x, LL n) {
LL ans = 1;
while(n) {
if(n & 1) ans *= x, ans %= mod;
x *= x, x %= mod;
n >>= 1;
}
return ans;
}
inline void sol() {
init();
LL g = __gcd(x, y);
for(int i = 0; P.arr[i] * P.arr[i] <= g; ++i) { //把x和y的最大公约数分解,把素因子存起来
if(g % P.arr[i] == 0) {
gcdd.push_back(P.arr[i]);
while(g % P.arr[i] == 0) g /= P.arr[i];
}
}
if(g > 1) gcdd.push_back(g);
mi.resize(gcdd.size());
fill(mi.begin(), mi.end(), 0);
LL tx = x, ty = y;
for(auto &i : gcdd) { //求出每个素因子在x和y中出现的次数
gnumx.push_back(0);
while(tx % i == 0) {
++gnumx.back();
tx /= i;
}
gnumy.push_back(0);
while(ty % i == 0) {
++gnumy.back();
ty /= i;
}
}
if(debug) {
_for(i, gcdd.size()) {
printf("%d: x %d, y %d\n", gcdd[i], gnumx[i], gnumy[i]);
}
}
LL ans = 1;
_rep(i, a, b) { //枚举每一个i,计算j的贡献
_for(x, gcdd.size()) {
LL numx = 0;
LL num = gnumx[x] * i;
LL m = num / gnumy[x];
if(m >= c) { //gcd的值主要被y的素因子个数所限制,等差数列求和
LL r = min(m, d);
numx += gnumy[x] * (r + c) * (r - c + 1) / 2;
}
if(d > m) { //gcd的值主要被x的素因子个数所限制,此时j的贡献是一段恒定的值
LL l = max(m + 1, c);
numx += num * (d - l + 1);
}
mi[x] += numx;
mi[x] %= (mod - 1);//费马小定理
}
}
_for(i, gcdd.size()) {
ans = ans * quickPow(gcdd[i], mi[i]) % mod;
}
printf("%lld\n", ans);
}
int main() {
//ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#ifdef ONLINE_JUDGE
#else
freopen("in.txt", "r", stdin);
debug = 1;
#endif
time_t beg, end;
if(debug) beg = clock();
P.doit(100000);
while(~scl(a)) {
scl(b), scl(c), scl(d), scl(x), scl(y);
sol();
}
if(debug) {
end = clock();
printf("time:%.2fs\n", 1.0 * (end - beg) / CLOCKS_PER_SEC);
}
return 0;
}
/*
in:
0 3000000 0 3000000
223092870
223092870
out:
824590783
*/