[Luogu4714] 「数学」约数个数和 [组合数学][k次前缀和][Pollard-ρ][Miller-Rabbin][乱搞]

Link
https://www.luogu.org/problemnew/show/P4714


Description
给定 n , k n,k n,k ,计算 ∑ d k ∣ n ∑ d k − 1 ∣ d k ⋯ ∑ d 4 ∣ d 5 ∑ d 3 ∣ d 4 ∑ d 2 ∣ d 3 ∑ d 1 ∣ d 2 1 \sum\limits_{d_k|n}\sum\limits_{d_{k-1}|d_k}\cdots\sum\limits_{d_4|d_5}\sum\limits_{d_3|d_4}\sum\limits_{d_2|d_3}\sum\limits_{d_1|d_2}1 dkndk1dkd4d5d3d4d2d3d1d21


Analysis
「你们竞赛就学这个啊?太简单了吧。」
这个式子很丑啊 你又不好化要是好化那直接a了
这种情况下,你要不考虑从实际意义分析,要不考虑从小情况开始
很明显每多一层 ∑ \sum 就会有一些东西被重复计数
是 什 么 呢 ?


举例吧。
2 2 3 3 5 2^23^35 22335
1 + 2 + 3 + 5 + 2 2 + 23 + 25 + 3 2 + 35 + 2 2 3 + 2 2 5 + 3 3 + 3 2 5 + 2 2 3 2 + 2 2 35 + 3 3 5 + 2 2 3 2 5 1+2+3+5+2^2+23+25+3^2+35+2^23+2^25+3^3+3^25+2^23^2+2^235+3^35+2^23^25 1+2+3+5+22+23+25+32+35+223+225+33+325+2232+2235+335+22325
推起来感觉很生成函数
( x 2 + x + 1 ) ( x 3 + x 2 + x + 1 ) ( x + 1 ) (x^2+x+1)(x^3+x^2+x+1)(x+1) (x2+x+1)(x3+x2+x+1)(x+1) ……(虽然实际上算的并不是这个——
3 ⋅ 4 ⋅ 1 3\cdot4\cdot1 341
多套一层 ∑ \sum
( 3 + 2 + 1 ) ( 4 + 3 + 2 + 1 ) ⋅ 1 (3+2+1)(4+3+2+1)\cdot1 (3+2+1)(4+3+2+1)1
( 3 + 2 + 1 + 2 + 1 ) ( 4 + 3 + 2 + 1 + 3 + 2 + 1 + 2 + 1 + 1 ) ⋅ 1 (3+2+1+2+1)(4+3+2+1+3+2+1+2+1+1)\cdot1 (3+2+1+2+1)(4+3+2+1+3+2+1+2+1+1)1
⋯ \cdots
我们考虑
f 0 ( 4 ) f_0(4) f0(4) → 4 + 3 + 2 + 1
f 1 ( 4 ) f_1(4) f1(4) → (4+3+2+1) + (3+2+1) + (2+1) + 1
¿
f k + 1 ( x ) = ∑ i = 1 x f k ( i ) f_{k+1}(x)=\sum\limits_{i=1}^{x}f_k(i) fk+1(x)=i=1xfk(i)
发现是一种 k k k 次前缀和的形式 ← 这是一个经典问题
所以一个 p i a i {p_i}^{a_i} piai被求和 k k k 次之后产生的贡献是一个 k k k 次前缀和
k k k 次前缀和的话,任意某个位置上的结果都是组合数
如果忘记具体的形式,画转移矩阵弄幂或者找规律打表都可以直接搞出答案
(貌似也可以直接用矩阵加速(反正都逃不过大数分解。。。


如果你没推过
好吧,怎么推?上面那个式子看着很可以搞事情啊
要求的东西是 f k ( a i ) f_k(a_i) fk(ai)
考虑
f − 2 ( 4 ) f_{-2}(4) f2(4) → 1
f − 1 ( 4 ) f_{-1}(4) f1(4) → 4
这种情况下我们可以看作有一堆点从 f − 2 f_{-2} f2 出发
求到达 f k ( a i ) f_k(a_i) fk(ai) 的方案数。
我们假设 f k ( a i ) f_k(a_i) fk(ai) 在某个诡异的(?)坐标系里的坐标 ( k , a i ) (k,a_i) (k,ai)
x = k , y = a i x=k,y=a_i x=k,y=ai

那么。
每次 x x x 必定 +1
每次 y y y 必定不变小
所以。相当于求 ∑ j Δ y j = a i \sum_j\Delta y_j=a_i jΔyj=ai
经典问题,用插板就可以解决。


更简单地
n = ∏ p i a i n=\prod{p_i}^{a_i} n=piai
I k ( n ) = ∏ ( a i + k + 1 a i ) = ∏ ( a i + k + 1 k + 1 ) = ∏ ( k + 2 ) ⋯ ( a i + k + 1 ) 1 ⋯ a i I^k(n)=\prod\binom{a_i+k+1}{a_i}=\prod{a_i+k+1\choose k+1}=\prod\frac{(k+2)\cdots(a_i+k+1)}{1\cdots a_i} Ik(n)=(aiai+k+1)=(k+1ai+k+1)=1ai(k+2)(ai+k+1)
啥意思?
k = 0 k=0 k=0 的时候很显然,就是在 a i + 1 a_i+1 ai+1 p i x {p_i}^x pix 里面选一个啊。 k = − 1 k=-1 k=1 就更不用说了。
k > 0 k>0 k>0 呢?相当于是在 a i + 1 a_i+1 ai+1 个空里插 k + 1 k+1 k+1 个板(空代表0到n次幂,插一个空就选了这个幂)
你问我怎么是插板?首先插板插下去的是无序的,所以可以一律当成是从(幂)大到小插进去的
这样的话就相当于(可重复地)插这么些个板板,一种插板方案对应了展开之后的一个因子
以上。

举个例子吧。
1 [ ] p [ ] p 2 [ ] p 3 [ ] 1[\quad]p[\quad]p^2[\quad]p^3[\quad] 1[]p[]p2[]p3[]
你随便插一下
1 [ ] p [    ∣      ] p 2 [    ∣ ∣    ] p 3 [ ] 1[\quad]p[\;|\;\;]p^2[\;||\;]p^3[\quad] 1[]p[]p2[]p3[]
就相当于是:
第一次展开 p 3 p^3 p3 的因子 p 2 p^2 p2
第二次展开 p 2 p^2 p2 的因子 p 2 p^2 p2
第三次展开 p 2 p^2 p2 的因子 p p p
这就是一种方案。


代码就比较精污了,你不得不上Pollard-ρ+Miller-Rabbin来分解N

回忆一下啊
Miller-Rabbin ① a ϕ ( p ) ≡ 1 ( m o d p ) a^{\phi(p)}\equiv1\pmod{p} aϕ(p)1(modp) 于是做费马素性检测 a p − 1 ≡ 1 a^{p-1}\equiv1 ap11
② 二次探测 a 2 ≡ 1 a^2\equiv1 a21 或者 a 2 ≡ − 1 a^2\equiv-1 a21
选取前 ⑨ 个素数(~23) 作为费马素性检测的 a a a 即可。

Pollard-ρ:忘了
利用 x 2 + α x^2+\alpha x2+α 生成伪随机数列 a n a_n an
利用循环节碰撞最小素因子 p p p (没办法搞 2 2 2 所以得单独做)
客观存在数列 a n m o d    p a_n\mod p anmodp 由生日悖论,循环节期望出现位置 O ( N ) O(\sqrt{N}) O(N )
具体:用两个指针 x x x 2 x 2x 2x a ∞ a_\infty a 上走
等一个 g c d ( a b s ( a x − a 2 x ) , P ) ≠ 1 gcd(abs(a_x-a_{2x}),P)\ne1 gcd(abs(axa2x),P)̸=1 ≠ P \ne P ̸=P 此时找到 P P P 的一个(有用哒)因子
按步批量处理gcd加速。


实际上并不需要严格分解成多个素数幂的乘积。尽量偷懒嗷
另外还可以先用 1 0 6 10^6 106 以内的素数筛 n n n 然后剩下的一定是 1    o r    p    o r    p q    o r    p 2 1\;or\;p\;or\;pq\;or\;p^2 1orporpqorp2
然后……搞一搞??玄学


话说我一开始写的超假的miller rabbin居然只挂了一个点。。。我这个pollardrho好像也有一点假
我的 码力 怎么 这么 差 啊 啊 啊
下面是 仍然可能有锅的代码
这个选手的代码可以拿去当wc题让人找错 论写一道题出几十个bug是什么感受

提醒一下,long double乘法取模最后要%p+p%p
不要问我为什么,虽然我觉着理论上它不会出负数。。。不过既然这是事实
我就煤办法腊()


#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<cctype>
#include<ctime>
using namespace std;
const long long p = 998244353;
const int MAXN = 100;
int Id[MAXN];
long long n, k, Pi[MAXN], Ai[MAXN], Pk[MAXN], Ak[MAXN];
int Prime[9] = {2, 3, 5, 7, 11, 13, 17, 19, 23};
long long gcd(const long long& a, const long long& b)
{
	return !b?a:gcd(b,a%b);
}
long long Mul(long long a, long long b, const long long& p)
{
	static long long t;
	t = (long double) a * b / p;
	return ((a * b - t * p) % p + p) % p;
}
long long qpowm(long long a, long long b, const long long& p)
{
	if (b <= 0) return 1;
	long long ret = 1;
	while (b)
	{
		if (b & 1) ret = Mul(ret, a, p);
		a = Mul(a, a, p);
		b >>= 1;
	}
	return ret;
}
long long PowList[100];
bool Miller(const long long& n)
{
	if (n == 2 || n == 3 || n == 5 || n == 7 || n == 11 || n == 13 || n == 17 || n == 19 || n == 23) return 1;
	if (n < 23) return 0;
	register bool flag;
	register long long sub = n - 1;
	while (!(sub&1)) sub >>= 1;
	for (register long long t, gkd, i = 0; i < 9; ++i)
	{
		gkd = sub; PowList[0] = 0; t = qpowm(Prime[i], gkd, n);
		while (gkd <= n - 1)
		{
			PowList[++PowList[0]] = t;
			t = Mul(t, t, n);
			gkd <<= 1;
		}
		if (PowList[PowList[0]] != 1) return 0;
		for (register int j = PowList[0] - 1; j >= 1; --j)
		{
			if (PowList[j] == n - 1) break;
			if (PowList[j] != 1) return 0;
		}
	}
	return 1;
}
long long Pollard_Find(const long long& n)
{
	register long long alph = 1, x, y, LastX, LastY, Step = pow(n, 0.1), Prod;
	while (true)
	{
		x = 2; y = 2;
		while (true)
		{
			LastX = x; LastY = y; Prod = 1;
			for (register long long i = 1; i <= Step; ++i)
			{
				x = Mul(x, x, n); x += alph; if (x >= n) x -= n;
				y = Mul(y, y, n); y += alph; if (y >= n) y -= n;
				y = Mul(y, y, n); y += alph; if (y >= n) y -= n;
				Prod = Mul(Prod, abs(x-y), n);
			}
			Prod = gcd(Prod, n);
			if (Prod == 1) continue;
			if (Prod != n) return Prod;
			x = LastX; y = LastY;
			for (register long long i = 1; i <= Step; ++i)
			{
				x = Mul(x, x, n); x += alph; if (x >= n) x -= n;
				y = Mul(y, y, n); y += alph; if (y >= n) y -= n;
				y = Mul(y, y, n); y += alph; if (y >= n) y -= n;
				Prod = gcd(n, abs(x-y));
				if (Prod == n) break;
				if (Prod != 1) return Prod;
			}
			break;
		}
		++alph;
	}
}
void Pollard_Rho(const long long& n)
{
	if (n <= 1) return;
	if (Miller(n)) { Pi[++Pi[0]] = n, ++Ai[Pi[0]]; return;}
	long long k = Pollard_Find(n);
	Pollard_Rho(k); Pollard_Rho(n/k);
}
inline bool cmp(const int& a, const int& b)
{
	return Pi[a] < Pi[b];
}
int main()
{
	scanf("%lld%lld", &n, &k);
	if (!(n&1))
	{
		Pi[++Pi[0]] = 2, Ai[1] = 1; n >>= 1;
		while (!(n&1)) n>>=1, ++Ai[Pi[0]];
	}
	Pollard_Rho(n);
	for (register int i = 1; i <= Pi[0]; ++i)
	{
		Id[i] = i;
	}
	sort(Id + 1, Id + 1 + Pi[0], cmp);
	if (Pi[0]) Pk[++Pk[0]]=Pi[Id[1]], Ak[1]=Ai[Id[1]];
	for (register int i = 2; i <= Pi[0]; ++i)
	{
		if (Pi[Id[i]] == Pi[Id[i-1]]) ++Ak[Pk[0]];
		else Pk[++Pk[0]] = Pi[Id[i]], Ak[Pk[0]] = Ai[Id[i]];
	}
	long long Ans = 1;
	for (register int i = 1; i <= Pk[0]; ++i)
	{
		for (register long long x = 1; x <= Ak[i]; ++x)
		{
			Ans = Mul(Ans, k + 1 + x, p);
			Ans = Mul(Ans, qpowm(x, p - 2, p), p);
		}
	}
	printf("%lld", Ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值