CF1334E Divisor Paths(数论)

探讨了在特定图中,基于质因数分解和最短路径算法的问题解决策略,利用pollard-rho算法进行质因数分解,通过计算因子的数量来确定最短路径长度,并提出了一种求解路径数目的方法。

题意

给定一个数 DDD,由 DDD 生成以下图:

  1. 每个点都为 DDD 的因子
  2. xxxyyy 连有无向边边当且仅当 x∣yx|yxyyx\frac{y}{x}xy 为素数
  3. (x,y)(x,y)(x,y) 的边权值为能整除 xxx 但不能整除 yyy 的数的个数

qqq 个询问,每个询问给出 u,vu,vu,v,求 uuuvvv 的最短路径数。
q≤300000,D≤1015q\leq 300000,D\leq 10^{15}q300000,D1015u,vu,vu,vDDD 的因子

分析

先来看看一条边的边权值,其实就是 d(x)−d(y)d(x)-d(y)d(x)d(y)。(d(x)d(x)d(x)xxx 的约数个数)
那么考虑 aaa 到其某个因子 bbb 的一条路径,最优走法肯定是每次走向 aaa 的一个因子。假设路径为 a−>x−>y−>ba->x->y->ba>x>y>b。那么路径长为 (da−dx)+(dx−dy)+(dy−db)=da−db(d_a-d_x)+(d_x-d_y)+(d_y-d_b)=d_a-d_b(dadx)+(dxdy)+(dydb)=dadb。因此,从 aaabbb 的最短路径长为 da−dbd_a-d_bdadb
那么考虑 uuuvvvuuu 先走到 tttttt 再走到 vvv。路径长即为 du+dv−2∗dtd_u+d_v-2*d_tdu+dv2dt。不难看出 tttgcd⁡(u,v)\gcd(u,v)gcd(u,v) 时,路径长最小。
那么最短路径数就是 way(u,t)∗way(t,v)way(u,t)*way(t,v)way(u,t)way(t,v)way(x,y)way(x,y)way(x,y)xxxyyy 的路径数。
考虑 way(x,y)way(x,y)way(x,y) 的求法。
我们将 x,yx,yx,y 表示成:
x=∏piki,y=∏pitix=\prod p_i^{k_i},y=\prod p_i^{t_i}x=piki,y=piti,由于走一条路径相当于每次去掉一个质因子。于是第 iii 个质数 pip_ipi 要走 ki−tik_i-t_ikiti 步,记为 cic_ici
那么这个问题就变成了一个多重集排列问题,方案数为 (∑ci)!∏(ci)!\frac{(\sum c_i)!}{\prod (c_i)!}(ci)!(ci)!
分解质因数采用 pollard−rhopollard-rhopollardrho 算法。
总复杂度为 O(D14+qlogD)O(D^{\frac{1}{4}}+qlogD)O(D41+qlogD)

代码如下

#include <bits/stdc++.h>
#include<ext/pb_ds/hash_policy.hpp>
#include<ext/pb_ds/assoc_container.hpp>
#define eps 1e-6
#define rg register
#define N 100005
using namespace __gnu_pbds;
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef long double LD;
const int mod = 998244353;
struct custom_hash {
    static uint64_t splitmix64(uint64_t x) {
        x += 0x9e3779b97f4a7c15;
        x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
        x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
        return x ^ (x >> 31);
    }
    size_t operator()(uint64_t x) const {
        static const uint64_t FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count();
        return splitmix64(x + FIXED_RANDOM);
    }
};
gp_hash_table<LL, int, custom_hash> g;
LL z = 1;
LL read(){
	LL x, f = 1;
	char ch;
	while(ch = getchar(), ch < '0' || ch > '9') if(ch == '-') f = -1;
	x = ch - '0';
	while(ch = getchar(), ch >= '0' && ch <= '9') x = x * 10 + ch - 48;
	return x * f;
}
inline LL mul(LL a, LL b, LL p){
	return (a * b - LL(LD(a) / p * b) * p + p) % p;
}
LL ksm(LL a, LL b, LL p){
	LL s = 1;
	while(b){
		if(b & 1) s = mul(s, a, p);
		a = mul(a, a, p);
		b >>= 1;
	}
	return s;
}
LL f(LL x, LL c, LL n){
	return (mul(x, x, n) + c) % n;
}
int mr(LL a, LL x){
	if(ksm(a, x - 1, x) != 1) return 0;
	LL y = x - 1, t;
	while(!(y & 1)){
		y >>= 1;
		t = ksm(a, y, x);
		if(t == x - 1) return 1;
		if(t != 1 && t != x - 1) return 0;
	}
	return 1;
}
inline int isprime(LL x){
	if(x == 2 || x == 3 || x == 5 || x == 7 || x == 43) return 1;
	if(!mr(2, x) || !mr(3, x) || !mr(5, x) || !mr(7, x) || !mr(43, x)) return 0;
	return 1;
}
inline LL Max(LL a, LL b){
	return a > b? a: b;
}
LL PR(LL x){
	LL s = 0, t = 0, c = rand() % (x - 1) + 1, d;
	LL val = 1;
	for(rg int goal = 1; ; goal <<= 1, s = t){
		for(rg int step = 1; step <= goal; step++){
			t = f(t, c, x);
			val = mul(val, abs(t - s), x);
			if(step % 127 == 0){
				d = __gcd(val, x);
				if(d > 1) return d; 
			}
		}
		LL d = __gcd(val, x);
		if(d > 1) return d;
	}
}
void fac(LL x){
	if(x < 2) return;
	if(isprime(x)){
		g[x]++;
		return;
	}
	LL p = x;
	while(p >= x) p = PR(x);
	fac(x / p), fac(p);
}
LL a[N], b[N], d[N], v[N], cnt, ret;
LL inv[N], Fac[N], maxn = N - 5;
LL way(LL u, LL v){
	LL s = 0, ans = 1, p, i, j;
	u /= v; 
	for(auto t: g){
		p = t.first; i = 0;
		while(u % p == 0) u /= p, i++, s++;
		ans = ans * inv[i] % mod;
	}
	ans = z * ans * Fac[s] % mod;
	return ans;
}
int main(){
	srand(time(0));
	LL i, k, x, q, u, v, d, s, ans = 1;
	for(Fac[0] = i = 1; i <= maxn; i++) Fac[i] = Fac[i - 1] * i % mod;
	inv[maxn] = ksm(Fac[maxn], mod - 2, mod);
	for(i = maxn - 1; i >= 0; i--) inv[i] = inv[i + 1] * (i + 1) % mod;
	x = read(); q = read();
	fac(x);
	while(q--){
		u = read(); v = read();
		d = __gcd(u, v);
		ans = way(u, d) * way(v, d) % mod;
		printf("%d\n", ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值