学习知识点发现的各种小tip

FFT,NTT

  1. 多项式的点值表示法是可以直接进行+,-,*,/等运算的,考虑其定义即可。但一般加减运算都是直接系数相加减,可以少做几次NTT/FFT,降低代码时间复杂度。(甚至有可能掩饰一些代码上的错误,比如有个数组进行了一次NTT+逆NTT后%xn,将大于xn的部分系数清零了,但没有影响你所需的(即n以内的)这部分系数的大小。此时再将这个长度已缩小到n的系数序列按第一次的序列长len=n<<1来做NTT就会产生错误的点值数组,但如果直接加减则没有问题);

  2. 进行FFT/NTT时,一定记得FFT/NTT与逆FFT/NTT所用的len相同。而且在一次FFT/NTT+逆FFT/NTT的过程没完成前不能更改存储点值/系数的数组,哪怕是超过当前所求长度n的部分改变,也会影响n以内的数据的正误;

  3. 进行多项式开方,求逆,ln,exp等运算时,如有不同的函数都要用中间数组,就多开几个不同的,别图省事。这样可以防止运行过程中仍需使用的数据被其他函数覆盖,也可以少清几个零。不过在不需卡常时,还是每次使用都把数组清空一遍,该赋初值的就赋初值。因为有些看着为零的变量/没被使用过的数组,不知道什么时候就被调用过了,多项式那么多函数套在一起,检查起来也费劲,还不如多写几行代码。

  4. 像赋初值,求len,求反转数组rev[i],多项式相乘的使用频率高的都写成函数,易读又好查错。

像上面那些显而易见的细节只有我这种蒟蒻才会需要吧……学多项式和生成函数感觉自己快退役了5555~

莫比乌斯反演

莫比乌斯反演中常见的做法有:

  1. ([gcd(x,y)==1])(varepsilon(gcd(x,y)))化为(Sigma_{p|gcd(x,y)}mu(p))
  2. 改变枚举顺序,将枚举因子提前,然后通常可以运用整除分块:(Sigma_{i=1}^nSigma_{j=1}^mSigma_{p|gcd(x,y)}mu(p)Rightarrow Sigma_{p=1}^{min(n,m)}mu(p)Sigma_{i=1}^{n/p}Sigma_{j=1}^{m/p}1Rightarrow Sigma_{p=1}^{min(n,m)}mu(p)lfloor frac np floor lfloor frac mp floor)
  3. 如果有类似(Sigma_{i=1}^nf(i)Sigma_{j=1}^{n/i}g(j)h(ij))的表达,可以尝试转化为(Sigma_{t=1}^nh(t)Sigma_{p|t}f(p)g(frac tp)),然后用数据结构维护(Sigma_{p|t}f(p)g(frac tp))

扩展中国剩余定理

  1. 中国剩余定理似乎可以用扩展中国剩余定理完全代替哦 废话,本来就是扩展嘛
  2. 扩展中国剩余定理有两种实现方法:联立和代入。现有两个方程:(x equiv a pmod {p_1},x equiv b pmod {p_2})

联立:(x=a+t_1*p_1=b+t_2*p_2),故 (t_1*p_1-t_2*p_2=b-a)。扩展欧拉定理得解。

代入:(x equiv a pmod {p_1}) 得出 (x+t_1*p_1=a)。求出特解(x=ans),可得通解(x=ans+t_1*p_1)。又有(x equiv b pmod {p_2}),即(ans+t_1*p_1+t_2*p_2=b) 移项得(t_1*p_1+t_2*p_2=b-ans),扩展欧拉定理得解。

当公式有变形时,似乎代入法能更简洁地得出解法:有 (n)个形如 (w_ixequiv a_i pmod {p_i})的同余方程,已得出前 (n-1)个的解 (ans)和模数 (P),第 (n)个方程为 (wx equiv a pmod p),代入可得 (w(ans+tP)+t_1p=a),移项得 (wPt+t_1p=a-w*ans)。求出特解 (t=T),则通解 (t=T+frac{p}{gcd(p,wP)}),则前 (n)个方程的解为 (ans_n=ans_{n-1}+TP),通解为 (ans_{n-1}+(T+frac{p}{gcd(p,wP)})*P),即 (ans_n+frac{pP}{gcd(p,wP)}),前 (n)个方程的模数为 (frac{pP}{gcd(p,wP)})。例题详见[NOI2018] 屠龙勇士做法。

路径计数问题

翻折法:求点A(0,0)到B(n,m),不经过某一条直线的不降路径条数(即只能向上或向右的路径),可作A关于直线的对称点A',答案即为A到B的不降路径条数-A'到B的不降路径条数。
证明:对于一条从A出发经过直线的路径,将其与直线最后的交点前的部分沿直线翻折,即可转化为一条从A'到B的路径。详见OI-wiki卡特兰数部分。

树形背包

有一类树形背包f[i][j],第一维表示点的编号,第二维表示子树内选的点数且限制不超过m。有如下转移:

void dfs(int u,int last) {
	sz[u]=1;
	for(int k=head[u];k;k=e[k].next)
	{
		int v=e[k].to; if(v==last) continue;
		dfs(v,u);
		for(int j=0;j<=min(m,sz[u]);++j)
			for(int k=0;k<=min(m-j,sz[v]);++k)
				dp[u][j+k]=dp[u][j] * dp[v][k];
	}
} 

其总复杂度可证为(O(nm))。具体见这篇博客(我没看懂,但我大受震撼)。

取模情况下大数组合数的计算

({n choose m}\%p)问题中若(n)(m)均较大,当然是使用卢卡斯定理;若只是(m)较大,答案为0;若只是(n)较大呢?
在一类特殊情况下可以直接对(n)取模:({nchoose m}*(m!))
难道不存在一种可能:(n)取模后变得小于(m),导致答案变成0,不同于原来了?实际上这是没关系的:

如果 (n \%mod) 后变得小于(m),那么证明 (n-val<m) ( (val) 为小于 (n) 的最大的(mod) 的倍数)。那么原式即 (n(n-1)(n-2)……(n-m+1))。这个算式中一定有一个 (mod) 的倍数,所以答案就是0。

如果 (n \%mod) 后仍大于(m),那么将 (n(n-1)……(n-m+1))的每一项 (\% mod) ,结果肯定仍相同。

原文地址:https://www.cnblogs.com/jstcao/p/14363148.html