E. Divisor Paths(数论+思维+多重集全排列)

题目

题意:

    给定一个数d,构造出一张图。点是d的所有因子,两个数x,y,若x|y并且x/y为素数,那么这两个点之间就有一条边,边权为d(x)-d(y)(d(x)为x的因子个数)。有q次访问,每个给定x,y。输出从x到y的最短路径条数。
     1 ≤ d ≤ 1 0 15 , 1 ≤ q ≤ 3 ⋅ 1 0 5 , 1 ≤ v , u ≤ d 1≤d≤10^{15},1≤q≤3⋅10^5,1≤v,u≤d 1d1015,1q31051v,ud

分析:

    首先我们考虑从x走到它的因子y,路径权值为d(x)-d(x1)+d(x1)-d(x2)…+d(xn)-d(y)=d(x)-d(y)。也就是说从一个点走到它的因子只与起点与终点有关。那么对于任意两个点,最短路径一定是从起点走到他们的gcd,然后走到终点。
    那我们就思考从一个点走到它的因子的最短路由多少种走法呢?根据题意可以知道,每走一步值会除以一个素因子。也就是说把差值素因子分解后,直接多重集的排列数就可以了。对于素因子的分解,开始我们直接分解d,因为所有点都是他的因子,所以直接用它的素因子来分解即可。
 
 

#include 
#include  
using namespace std;

typedef long long ll;

ll mod = 998244353;

vector<ll> num;
ll f[105],invf[105];

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

ll q_pow(ll a,ll b)
{
	ll res = 1;
	while( b )
	{
		if( b & 1 ) res = ( res * a ) % mod;
		a = ( a * a ) % mod;
		b >>= 1;
	}
	return res;
}

ll inv(ll a)
{
	return q_pow(a,mod-2);
}

void init(int n)
{
	f[0] = 1;
	invf[0] = inv(f[0]);
	for (int i = 1; i <= n; i++)
	{
		f[i] = f[i-1] * i;
		f[i] %= mod;
		invf[i] = inv(f[i]);
	}
}

ll slove(ll a)
{
	ll res = 1;
	ll temp = 0;
	for (int i = 0; i < num.size(); i++)
	{
		ll z = 0;
		while( a % num[i] == 0 )
		{
			a /= num[i];
			z ++;
		}
		res *= invf[z];
		res %= mod; 
		temp += z;
	}
	res *= f[temp];
	res %= mod;
	return res;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	ll d,q;
	cin >> d >> q;
	ll a = d;
	init(100);
	for (ll i = 2; i * i <= d; i++)
	{
		if( a % i == 0 )
		{
			num.push_back(i);
			while( a % i == 0 ) a /= i;
		}
	}
	if( a > 1 ) num.push_back(a);
	for (int i = 1; i <= q; i++)
	{
		ll x,y;
		cin >> x >> y;
		ll g = gcd(x,y);
		cout << ( slove(x/g) * slove(y/g) ) % mod << '\n'; 
	}
	return 0;
}

你可能感兴趣的:(cf刷题)