[Luogu4714] 「數學」約數個數和 [組合數學][k次前綴和][Pollard-ρ][Miller-Rabbin][亂搞]

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


Description
給定 n,kn,k ,計算 dkndk1dkd4d5d3d4d2d3d1d21\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


Analysis
「你們競賽就學這個啊?太簡單了吧。」
這個式子很醜啊 你又不好化要是好化那直接a了
這種情況下,你要不考慮從實際意義分析,要不考慮從小情況開始
很明顯每多一層 \sum 就會有一些東西被重複計數
是 什 麼 呢 ?


舉例吧。
223352^23^35
1+2+3+5+22+23+25+32+35+223+225+33+325+2232+2235+335+223251+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
推起來感覺很生成函數
(x2+x+1)(x3+x2+x+1)(x+1)(x^2+x+1)(x^3+x^2+x+1)(x+1) ……(雖然實際上算的並不是這個——
3413\cdot4\cdot1
多套一層 \sum
(3+2+1)(4+3+2+1)1(3+2+1)(4+3+2+1)\cdot1
(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
\cdots
我們考慮
f0(4)f_0(4) → 4 + 3 + 2 + 1
f1(4)f_1(4) → (4+3+2+1) + (3+2+1) + (2+1) + 1
¿
fk+1(x)=i=1xfk(i)f_{k+1}(x)=\sum\limits_{i=1}^{x}f_k(i)
發現是一種 kk 次前綴和的形式 ← 這是一個經典問題
所以一個 piai{p_i}^{a_i}被求和 kk 次之後產生的貢獻是一個 kk 次前綴和
kk 次前綴和的話,任意某個位置上的結果都是組合數
如果忘記具體的形式,畫轉移矩陣弄冪或者找規律打表都可以直接搞出答案
(貌似也可以直接用矩陣加速(反正都逃不過大數分解。。。


如果你沒推過
好吧,怎麼推?上面那個式子看着很可以搞事情啊
要求的東西是 fk(ai)f_k(a_i)
考慮
f2(4)f_{-2}(4) → 1
f1(4)f_{-1}(4) → 4
這種情況下我們可以看作有一堆點從 f2f_{-2} 出發
求到達 fk(ai)f_k(a_i) 的方案數。
我們假設 fk(ai)f_k(a_i) 在某個詭異的(?)座標系裏的座標 (k,ai)(k,a_i)
x=k,y=aix=k,y=a_i

那麼。
每次 xx 必定 +1
每次 yy 必定不變小
所以。相當於求 jΔyj=ai\sum_j\Delta y_j=a_i
經典問題,用插板就可以解決。


更簡單地
n=piain=\prod{p_i}^{a_i}
Ik(n)=(ai+k+1ai)=(ai+k+1k+1)=(k+2)(ai+k+1)1aiI^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}
啥意思?
k=0k=0 的時候很顯然,就是在 ai+1a_i+1pix{p_i}^x 裏面選一個啊。 k=1k=-1 就更不用說了。
k>0k>0 呢?相當於是在 ai+1a_i+1 個空裏插 k+1k+1 個板(空代表0到n次冪,插一個空就選了這個冪)
你問我怎麼是插板?首先插板插下去的是無序的,所以可以一律當成是從(冪)大到小插進去的
這樣的話就相當於(可重複地)插這麼些個闆闆,一種插板方案對應了展開之後的一個因子
以上。

舉個例子吧。
1[]p[]p2[]p3[]1[\quad]p[\quad]p^2[\quad]p^3[\quad]
你隨便插一下
1[]p[      ]p2[    ]p3[]1[\quad]p[\;|\;\;]p^2[\;||\;]p^3[\quad]
就相當於是:
第一次展開 p3p^3 的因子 p2p^2
第二次展開 p2p^2 的因子 p2p^2
第三次展開 p2p^2 的因子 pp
這就是一種方案。


代碼就比較精污了,你不得不上Pollard-ρ+Miller-Rabbin來分解N

回憶一下啊
Miller-Rabbin ① aϕ(p)1(modp)a^{\phi(p)}\equiv1\pmod{p} 於是做費馬素性檢測 ap11a^{p-1}\equiv1
② 二次探測 a21a^2\equiv1 或者 a21a^2\equiv-1
選取前 ⑨ 個素數(~23) 作爲費馬素性檢測的 aa 即可。

Pollard-ρ:忘了
利用 x2+αx^2+\alpha 生成僞隨機數列 ana_n
利用循環節碰撞最小素因子 pp (沒辦法搞 22 所以得單獨做)
客觀存在數列 anmod  pa_n\mod p 由生日悖論,循環節期望出現位置 O(N)O(\sqrt{N})
具體:用兩個指針 xx2x2xaa_\infty 上走
等一個 gcd(abs(axa2x),P)1gcd(abs(a_x-a_{2x}),P)\ne1P\ne P 此時找到 PP 的一個(有用噠)因子
按步批量處理gcd加速。


實際上並不需要嚴格分解成多個素數冪的乘積。儘量偷懶嗷
另外還可以先用 10610^6 以內的素數篩 nn 然後剩下的一定是 1  or  p  or  pq  or  p21\;or\;p\;or\;pq\;or\;p^2
然後……搞一搞??玄學


話說我一開始寫的超假的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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章