一:扩展欧拉定理的一种更简便的写法
void exgcd(int a,int b,int &d,int &x,int &y){ if(b) exgcd(b, a % b, d, y, x), y -= x * (a / b); else x = 1, y = 0, d = a; }
例:OJ 1135
二:线性求逆元,若$a*i+b=p,a=\lfloor \frac{p}{i} \rfloor,b=p%i$则$\frac{1}{i}=-\frac{a}{b}$
inv[1] = 1; for(int i=2; i<=n; i++) inv[i] = p - (p / i) * inv[p % i] % p;
三:大步小步算法,求$a^x=b(mod p)$,令$k=\sqrt{p},0<c<=k,0<=d<k$,原式可转化为$a^{ck-d}=b$,即$a^{ck}=b*a^d$,用hash表保存每一个$b*a^d$,最后依次检验即可
时间复杂度$O(\sqrt{n})$
例题:洛谷 3846
四:牛顿迭代法,令$x_0$为一个任意值,重复令$x_0=x_0-\frac{f(x_0)}{f^{'}(x_0)}$,最后$x_0$会稳定在函数f的零点上,这个方法大部分情况下成立(没有固定的规律)
例:10次循环计算根号(非常快):
int main(){ while(1){ double c, x0 = 1; scanf("%lf", &c); if(!c) break; for(int i=1; i<=10; i++) x0 = (x0 + c / x0) / 2; printf("%.12lf\n", x0); } return 0; }
五:(巨坑)树的计数
1:有标号无根树计数:$Ans = n^{n-2}$,可以考虑prufer序列,序列中的x的数量+1就是图中点的度数,以此来考虑问题
2:有标号有根树计数:$Ans = n*n^{n-2}=n^{n-1}$,就是有标号无根树情况中以每个点为根,所以答案乘以n
3:无标号有根树计数:令$f_k$为k个点的树的数量,转移方程$f_k = \frac{\sum_{i=1}^{k-1} \ \ f_i \sum_{j|k-i} \ \ \ jf_j}{n-1}$
设生成函数$F(x)=x\prod_{i=1}^{\infty}(\frac{1}{1-x^i})^{f_i}$
两边取对数得$lnF(x)=lnx-f_i\sum_{i=1}^{\infty}({1-x^i})$
两边求导得$\frac{F^{'}(x)}{F(x)}=\frac{1}{x}+\sum_{i=1}^{\infty}if_i\frac{x^{i-1}}{1-x^i}$
化简得$xF^{'}(x)=F(x)+F(x)\sum_{i=1}^{\infty}\frac{x^i}{1-x^i}$
对比$x^n$系数得$nf_n=f_n+\sum_{i-1}^{\infty}if_i\sum_{j=ik,k\in N^+}f_{n-j}$
最终化简得到$f_k = \frac{\sum_{i=1}^{k-1} \ \ f_i \sum_{j|k-i} \ \ \ jf_j}{n-1}$
朴素算法时间复杂度是$O(n^2)$,但这里还有一个$O(nlog^2n)$的算法
CDQ分治在这里又排上用场了,设$g(x)==\sum{j|x}jf_j$,则$(n-1)f_n=\sum_{i=1}^{n-1}f_ig_{n-i}$,这不就是卷积的形式吗
先计算$[1,\frac{n}{2}]$的f,之后每计算完成一个f就更新g,因为g(x)只与$j|x的f(j)$有关,所以$\frac{n}{2}$之后的g(x)实际上已经更新完了,所以就可以直接用FFT乘法计算左边对右边的贡献,最后再计算右边之间的贡献就可以了
4:无标号无根树计数:令这个图的重心为根,则它的子树的大小都小于$\frac{n}{2}$,所以答案就是有根树数量减去子树大小大于$\frac{n}{2}$的有根数的数量,注意当n为偶数时要额外减去重心有两个点的情况
例:OJ 1301 1305
#include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> using namespace std; inline long long read(){ long long ans = 0, f = 1; char ch = getchar(); while(!isdigit(ch)) f *= (ch == '-') ? -1 : 1, ch = getchar(); do ans = (ans << 1) + (ans << 3) + (ch ^ 48), ch = getchar(); while(isdigit(ch)); return ans * f; } const int MAXN = 501, MOD = 998244353; int a[MAXN], C[MAXN][MAXN], Dp[MAXN][MAXN]; int main(){ int n = read(); for(int i=0; i<=n; i++) for(int j=0; j<=i; j++) C[i][j] = (j == 0 || j == i) ? 1 : (C[i-1][j-1] + C[i-1][j]) % MOD; for(int i=1; i<=n; i++) a[i] = read(); Dp[0][0] = 1; for(int i=1; i<=n; i++) for(int j=0; j<=n-2; j++) for(int k=0; k<=min(j,a[i]-1); k++) Dp[i][j] = ((long long)C[j][k] * Dp[i-1][j-k] + Dp[i][j]) % MOD; printf("%d", Dp[n][n-2]); return 0; }
#include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> using namespace std; inline long long read(){ long long ans = 0, f = 1; char ch = getchar(); while(!isdigit(ch)) f *= (ch == '-') ? -1 : 1, ch = getchar(); do ans = (ans << 1) + (ans << 3) + (ch ^ 48), ch = getchar(); while(isdigit(ch)); return ans * f; } const int MAXN = 2001, MOD = 998244353; int Dp[MAXN], g[MAXN]; int pow(int a,int x){ int ans = 1; while(x){ if(x & 1) ans = (long long)ans * a % MOD; a = (long long)a * a % MOD, x >>= 1; } return ans; } int main(){ int n = read(); Dp[1] = 1, g[1] = 1; for(int i=2; i<=n; i++){ for(int j=1; j<i; j++) Dp[i] = ((long long)g[i-j] * Dp[j] + Dp[i]) % MOD; Dp[i] = (long long)Dp[i] * pow(i - 1, MOD - 2) % MOD; for(int j=1; j<=i; j++) if(i % j == 0) g[i] = ((long long)Dp[j] * j + g[i]) % MOD; } printf("%d", Dp[n]); return 0; }