2019 ICPC xuzhou E - Multiply(大数因数分解Pollard_rho 算法)

题目链接
题意:
\(Z=a_1!a_2!a_3!...a_n!\)
\(b_i=Z*X^i\)
求最大的\(i\)使得\(b_i\)\(Y!\)的因子。
思路:
\(a_1+a_2+a_+...+a_n<Y\),不难证明:\(Z\)一定为 \(Y!\)的因子。
所以直接对\(X\)进行素因子分解,对每个因子\(p_i\)求得其在\(Z\)\(Y!\)中的幂次数\(sumZ_i,sumY_i\),则\(sumY_i-sumZ_i\)\(p_i\)\(X^i\)中出现的最大幂次数。
\(ans=min(\dfrac{sumY_i-sumZ_i}{sump_i})\)(\(sump_i\)\(p_i\)\(X\)中的幂次数)

code:

#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
#include <cmath>
#include <map>
#include <set>

#define fi first
#define se second
#define pb push_back
// #define endl "\n"
#define debug(x) cout << #x << ":" << x << endl;
#define bug cout << "********" << endl;
#define all(x) x.begin(), x.end()
#define lowbit(x) x & -x
#define fin(x) freopen(x, "r", stdin)
#define fout(x) freopen(x, "w", stdout)
#define ull unsigned long long
#define ll long long

const double eps = 1e-5;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double pi = acos(-1.0);
const int mod = 1e9 + 7;
const int maxn = 2e5 + 10;

using namespace std;

int n;
ll s[maxn], x, y, z;
ll tot, p[maxn];

ll gcd(ll a, ll b){return b == 0 ? a : gcd(b, a % b);}

ll qmul(ll a, ll b, ll p){//快速乘
    ll z = (long double)a / p * b;
    ll ret = (ull)a * b - (ull)z * p;
    return (ret + p) % p;
}

ll qpow(ll a, ll b, ll p){//快速幂
    ll ret = 1;
    while(b){
        if(b & 1)ret = qmul(ret , a , p);
        b >>= 1;
        a = qmul(a, a, p);
    }
    return ret;
}

bool Miller_rabin(ll n){// Miller _ Rabin判断素数
    if(n == 2)return true;
    if(n < 2 || !(n & 1))return false;//特判
    ll m = n - 1, k = 0;
    while(!(m & 1))m >>= 1, k ++;// 求得2的幂次数
    for(int i = 1; i <= 6; i ++){// 测试次数
        ll a = rand() % (n - 1) + 1;// 随机生成a
        ll x = qpow(a, m, n), y;
        for(int j = 1; j <= k; j ++){
            y = qmul(x, x, n);
            if(y == 1 && x != 1 && x != n - 1)return false;
            // a ^ 2 = 1(mod p), p为质数,若a不为 1或者 n - 1, 则不符合二次探测定理
            x = y;
        }
        if(y != 1)return false;// 费马小定理的逆命题判断
    }
    return true;
}

ll Pollard_Rho(ll n){// 二次优化,减少了gcd的计算次数
    ll z, x, y, g, c;
    while(1){// 一定会找到一个因子
        int i = 0, j = 1;
        c = rand() % (n - 1) + 1;
        y = x = rand() % (n - 1) + 1;// 随机初始化
        z = 1;// 存 abs(x - y)的乘积
        while(++ i){
            x = (qmul(x, x, n) + c) % n;// x ^ 2 + c
            z = qmul(z, abs(x - y), n);
            if(!z || x == y)break; // z = 0时重新测试, x == y时即为跑完了环,也重新测试
            if(i == j || i % 127 == 0){// 当i == j 或者 i % 127 = 0 时,判断gcd
                g = gcd(n, z);
                if(g > 1)return g;// 找到了一个因子
                if(i == j)j <<= 1, y = x; // 倍增维护答案准确性
            }
        } 
    }
}

void pr(ll n){
	if(n == 1)return ;
    if(Miller_rabin(n)){
		p[++ tot] = n;
		return ;
	}
    ll a = Pollard_Rho(n);
    while(n % a == 0)n /= a;
    pr(a), pr(n); // 继续分解
}

ll cnt(ll x, ll p){
	if(!x)return 0;
	return x/p + cnt(x/p, p);
}

int main(){
	int t;
	scanf("%d", &t);
	while(t --){
		scanf("%d%lld%lld", &n, &x, &y);
		for(int i = 1; i <= n; i ++)scanf("%lld", &s[i]);
		tot = 0, pr(x);
		ll ans = INF;
		for(int i = 1; i <= tot; i ++){
			ll ret = x, sum = 0;
			while(ret % p[i] == 0)ret /= p[i], sum ++;

			ll a = cnt(y, p[i]), b = 0;
			for(int j = 1; j <= n; j ++){
				b += cnt(s[j], p[i]);
			}

			ans = min(ans, (a - b)/sum);
		}
		printf("%lld\n", ans);
	}
	return 0;
}
posted @ 2021-10-30 17:22  lniiwuw  阅读(30)  评论(0)    收藏  举报