优化 half-gcd 常数

https://arxiv.org/ftp/arxiv/papers/2212/2212.12389.pdf 的一点理解.

(radix-2的)fft模型下:

利用的和fft有关的性质较基础,主要是 fft-doubling 和 循环卷积.

其他模型下:

没看.

有多项式 \(P,Q\) 满足 \(deg(P)>deg(Q)\).

定义 Euclidean remainder sequence(欧几里得余数序列?)为.

\(R_{0}:=P\)
\(R_{1}:=Q\)
\(R_{k+1}:=R_{k-1}%R_{k}\)

定义余数序列的长度 \(l\) 为最小的 \(l\) 满足 \(R_l=0\) (这可看作整个欧几里得过程.)

余数序列上多项式度数是递减的,如果它每次恰减少1称其是normal的(另外,对于\(P=998244353,n=10^5\),随机数据取模一次后基本满足).

\(A_k(1<=k<=l)\)是1x2的向量\( \left[ \begin{array}{ccc} R_{k-1} \\ R_K \end{array} \right] \)

(欧几里得过程中的一步.)

定义Bezout matrices(约减矩阵?)是一个2x2的矩阵其为

\(B_k:= \left[ \begin{array}{ccc} 0 & 1\\ 1 & -R_{k-1}/R_{k} \end{array} \right] \)

(\(1<=k<=l-1\))

不妨令\(B_{i:j} = B_{j-1} * B_{j-2} ... * B_{i+1} * B_{i}\)

其满足

\(A_j = B_{i:j} * A_i\)

则有 $A_l = \left[
\begin{array}{ccc}
\gcd(P,Q) \
0
\end{array}
\right] = B_{1:l} *
\left[
\begin{array}{ccc}
P \
Q
\end{array}
\right] $

那么实际上要求出 \(B_{1:l}\).

引理1:

\(deg(B_{i:j})=deg(B_{i})+...+deg(B_{j-1})\)

又有 \(deg(R_i)=d-deg(B_{1;i+1})\)

若余数序列是normal的,则有更强的结论:

\(deg(B_{i;j})=j-i,deg(R_i)=deg(P)-i\).

证明:

归纳法可得 \(deg((B_{i;j})_{2,2})\) 大于矩阵中其余多项式的度.

然后再用一次归纳法即可.

考虑normal情况下的计算.

引理2:

normal的余数序列前 \(k\) 项只与最高的 \(2k\) 项有关.

证明:

大概思想是基于 \(deg(B_{i;j})=j-i\),\(deg(R_i)=d-i\) 考察贡献.

算法1:对于余数序列normal的\(P,Q(deg(P)>deg(Q))\)计算 \(B_{1;k+1}\)

func calc1(poly P,poly Q,int k)->mat2x2<poly>
	let d=deg(P)
	if k=1
		ret {{0,1},{1,-P[d-2,)/Q[d-2)}}^T
	let h=ceil(k/2),h'=k-h
	let M=calc1(P[d-2h,),Q[d-2h,),h)
	let {P',Q'}^T=M*{P,Q}^T
	let M'=calc1(P'[d-h-2h'),Q'[d-h-2h'),h')
	ret M'M

证明:根据引理2得到.

优化主要建立在对中间产物的利用上.

考察如何优化计算 \(P'[d-h-2h'),Q'[d-h-2h')\)

在我们的模型下

注意到\(deg(M),deg(M')\)不超过h

\(P\),\(Q\)分块计算,利用循环卷积优化.

不过\(P'\)\(MM'\)的精度(可能)少一项,可以强行求出这一项.(总可以线性求出两个多项式乘积的一项.)

我们使用 "fft缓存" 的方式使用doubling fft.以减少1/3的开销.

基于这些优化,大概能比朴素实现减少2/3的fft长度.(矩阵乘法部分的复用可以让开销*1/2)

这里不给出normal情况下如何优化的细节,可直接参照not normal的情况.

考虑not normal的情况.

先对余数序列进行重标号.

对于一个 not normal 的余数序列,我们把单次减少度数为 \(d\) 的拆成 \(d\) 步,实际的约减在最后一步做.

而其他部分满足\(R_k\)\(A_k\)不变,\(B_k\)为单位矩阵,记作\(R*_k\),\(A*_k\),\(B*_k\).

这保证乘积关系等仍然成立.

现在我们的问题在于,第一轮递归之后不一定把度都消了.

(换言之,这时计算的\(P'\)不一定满足\(deg(P')=d-h\))

但如果没有消掉,一次除法可以补足这个问题.

(因为从定义发现此时\(deg(Q')<d-h\))

所以给出 not normal 的情况

(我们记单位矩阵为I)
对于\(P,Q(deg(P)>deg(Q))\)且满足\(k<=deg(P)\)计算 \(B*_{1;k+1}\)

func calc2(poly P,poly Q,int k)->mat2x2<poly>
	let d=deg(P)
	if deg(Q)<d-k
		ret I
	if k=1
		ret {{0,1},{1,-P[d-2,)/Q[d-2)}}^T
	let h=ceil(k/2),h'=k-h
	let M=calc2(P[d-2h,),Q[d-2h,),h)
	let delta=h-deg(M)
	let {P',Q'}^T=M*{P,Q}^T
	//此时应当有
	//deg(P')=d-h+delta 
	//deg(Q')<d-h
	if deg(Q')<d-k
		ret M
	if delta > 0
		//not normal
		let D=P'[d-h-2h'-delta,)/Q'[d-h-2h'-delta,)
		//D同样等于P'/Q'
		M={{0,1},{1,-D}}^T*M
		{P',Q'}->{Q',P'-Q'D}
		//可能更具有启发性的写法是 {P',Q'}->{Q',P'%Q'}
	let h''=k-deg(M)
	let M'=calc2(P'[d-k-h''),Q'[d-k-h''),h'')
	ret M'M

因为每次除法约减掉的度至少是除法长度.

所以除法的总有效长度不会超过\(deg(P)\)因此精细实现下被摊掉了,不是复杂度瓶颈.

(btw:暴力\(\gcd\)也是,暴力\(\gcd\)的复杂度来自于求余.)

(除法有效长度是说对于\(quo(P,Q)\)实际上求逆的长度只需\(deg(P)-deg(Q)+1\)就足够.)

not normal 情况下的优化思想是类似的,还是考虑利用中间产物.

这里给出一个比较详细的伪代码.

对于\(P,Q(deg(Q)<deg(P))\)且满足\(k<=l,l=2^N,k<=deg(P)\)计算 \(B*_{1;k+1}\)\(dft(B*_{1;k+1})\).

func calc3(poly P,poly Q,int k,int l)->mat2x2<poly>,mat2x2<dft_poly>
	let d=deg(P)
	if deg(Q)<d-k
		ret I,dft(I,l)
	if 2k<=l
		let M,dft_M=calc3(P,Q,k,l/2)
		ret M,dft_doubling(dft_M)
	if l=1
		let B={{0,1},{1,-P[d-2,)/Q[d-2)}}^T
		ret B,dft(B,l)
	let h=l/2,h'=k-h
	let M,dft_M=calc3(P[d-2h,),Q[d-2h,),h,l)
	let delta=h-deg(M)
	let {P',Q'}^T=M*{P,Q}^T
	//此时应有
	//deg(P')=d-h+delta 
	//deg(Q')<d-h
	//用分块乘法和循环卷积并用已得的点值计算{P'[d-3h-delta,),Q'[d-3h-delta,)}
	//3h+delta-k
	//这里可能需要补齐余项
	//不要真把P',Q'算出来,不然白优化了.
	//正确的实现下,要计算的P'部分的长度不超过2h+2delta+1,Q'的长度不超过2h+delta
	//计算这两个只需要8E(l).
	//可以发现只有P'[d-3h-delta,),Q'[d-3h-delta,)会用到.
	//为了方便理解仍使用P',Q'标注
	if deg(Q')<d-2h 
		ret M,dft_M
		//论文是2h,但是(按我的理解)k就够了,用2h可能需要处理一下边界问题.
	// deg(Q')>=d-l
	let h''=k-deg(M)
	if delta > 0
		let D=P'[d-h-2h'-delta,)/Q'[d-h-2h'-delta,)
		//let deg(P)=deg(Q)+delta+neta
		//deg(D)=delta+neta
		let J={{0,1},{1,-D}}^T
		let dft_D=dft(D,l),dft_J=dft(J,l)
		dft_M=dft_J*dft_M
		//dft_J无需单独算,其中三项是常数,剩下的用dft_D拼上.
		h''=h'+delta-deg(J)
		{P',Q'}->{Q',P'-Q'D}
		//计算P'-Q'D
		//精细实现(分块乘法)下需要2E(l) + O(M(delta+neta))
		//O(M(delta+neta)) 部分可摊掉.
	//P'[d-3h-delta,),Q'[d-3h-delta,)
	//3h+delta-k-h''
	//h''<=h'<=h
	let M',dft_M'=calc3(P'[d-k-h''),Q'[d-k-h''),h'',l)
	ret M'M dft_M'*dft_M 
	//这里可能需要补齐余项
	//使用已计算出的点值,复杂度4E(l)

实际实现:

主要由killer_joke实现

以下部分是他写的.

不优化版实现起来其实还挺简单的.

优化版实现起来相当恶心,主要是余项很,非常,尤其麻烦.

追求效率建议自己实现一个小范围暴力,只对大范围用这个算法.

loj上的板子和yosupo上的板子数据都有一部分情况没有考虑到.

(包括但不限于 yosupo上没有\(n\),\(m\)差距极端大的情况,loj上没有基于\(\gcd\)流程构造的not normal数据.)

我写假了的算法经常能分别过其中一个.

这份实现不能保证正确,但是我尽力了.

分块计算\(P'-Q'D\)实在是不好写,这部分是暴力实现的.

没有精细实现通过均摊复杂度减少\(log_2 k M(k)\)这一项的常数.

yosupo和loj都没有把这个算法常数卡满的数据.如果需要的话可以找我要(bushi).

给出在loj上的提交记录:https://loj.ac/s/1951025

posted @ 2023-12-09 10:49  QedDust  阅读(73)  评论(0)    收藏  举报