E Groundhog Chasing Death(2020牛客暑期多校训练营(第九场))(思维+费马小定理+质因子分解)

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=abj=cdgcd(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=abj=cdgcd(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,00a,b,c,d3×106,0<x,y109,ab,cd.

题意

给出 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=abj=cdgcd(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(p1)1(mod p),把素因子的个数对“mod-1”取模即可。时间复杂度 O ( n   l o g n ) O(n\ log n) O(n logn)

最后利用快速幂算出每个幂的值,然后求乘积即可。

代码

#pragma GCC optimize(2)
#include 
#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

*/

你可能感兴趣的:(找规律,线性筛,数论)