数学

组合数学

组合数

常用公式

( n k ) = n k ( n − 1 k − 1 ) \left(\begin{array}{l}n \\k\end{array}\right)=\frac{n}{k}\left(\begin{array}{l}n-1 \\k-1\end{array}\right) (nk)=kn(n1k1)
( n m ) = ( n − 1 m ) + ( n − 1 m − 1 ) \left(\begin{array}{l}n \\m\end{array}\right)=\left(\begin{array}{c}n-1 \\m\end{array}\right)+\left(\begin{array}{l}n-1 \\m-1\end{array}\right) (nm)=(n1m)+(n1m1)
( n 0 ) + ( n 1 ) + ⋯ + ( n n ) = ∑ i = 0 n ( n i ) = 2 n \left(\begin{array}{l}n \\0\end{array}\right)+\left(\begin{array}{l}n \\1\end{array}\right)+\cdots+\left(\begin{array}{l}n \\n\end{array}\right)=\sum_{i=0}^{n}\left(\begin{array}{l}n \\i\end{array}\right)=2^{n} (n0)+(n1)++(nn)=i=0n(ni)=2n
∑ i = 0 n ( − 1 ) i ( n i ) = 0 \sum_{i=0}^{n}(-1)^{i}\left(\begin{array}{l}n \\i\end{array}\right)=0 i=0n(1)i(ni)=0
∑ i = 0 m ( n i ) ( m m − i ) = ( m + n m ) ( n ≥ m ) \sum_{i=0}^{m}\left(\begin{array}{c}n \\i\end{array}\right)\left(\begin{array}{c}m \\m-i\end{array}\right)=\left(\begin{array}{c}m+n \\m\end{array}\right) \quad(n \geq m) i=0m(ni)(mmi)=(m+nm)(nm)
∑ i = 0 n ( n i ) 2 = ( 2 n n ) \sum_{i=0}^{n}\left(\begin{array}{l}n \\i\end{array}\right)^{2}=\left(\begin{array}{c}2 n \\n\end{array}\right) i=0n(ni)2=(2nn)
∑ i = 0 n i ( n i ) = n 2 n − 1 \sum_{i=0}^{n} i\left(\begin{array}{l}n \\i\end{array}\right)=n 2^{n-1} i=0ni(ni)=n2n1
∑ i = 0 n i 2 ( n i ) = n ( n + 1 ) 2 n − 2 \sum_{i=0}^{n} i^{2}\left(\begin{array}{l}n \\i\end{array}\right)=n(n+1) 2^{n-2} i=0ni2(ni)=n(n+1)2n2
∑ l = 0 n ( l k ) = ( n + 1 k + 1 ) \sum_{l=0}^{n}\left(\begin{array}{l}l \\k\end{array}\right)=\left(\begin{array}{l}n+1 \\k+1\end{array}\right) l=0n(lk)=(n+1k+1)
( n r ) ( r k ) = ( n k ) ( n − k r − k ) \left(\begin{array}{l}n \\r\end{array}\right)\left(\begin{array}{l}r \\k\end{array}\right)=\left(\begin{array}{l}n \\k\end{array}\right)\left(\begin{array}{l}n-k \\r-k\end{array}\right) (nr)(rk)=(nk)(nkrk)
∑ i = 0 n ( n − i i ) = F n + 1 \sum_{i=0}^{n}\left(\begin{array}{c}n-i \\i\end{array}\right)=F_{n+1} i=0n(nii)=Fn+1

求组合数

杨辉三角

O ( n 2 ) O(n^2) O(n2)求出 C ( 0 ⋯ n , 0 ⋯ n ) C(0\cdots n, 0 \cdots n) C(0n,0n)
C ( n , k ) = C ( n − 1 , k ) + C ( n − 1 , k − 1 ) C(n, k)=C(n-1, k)+C(n-1, k-1) C(n,k)=C(n1,k)+C(n1,k1)

Lucas定理

C ( n , m ) % p = C ( n p , m p ) ∗ C ( n % p , m % p ) % p C(n, m) \% p=C\left(\frac{n}{p}, \frac{m}{p}\right) * C\left(n \% p, m \% p\right) \% p C(n,m)%p=C(pn,pm)C(n%p,m%p)%p

int Lucas(LL n, LL m, LL p){
	if (m == 0) return 1;
	return Cm(n % p, m % p, p) * Lucas(n / p, m / p, p) % p;
}
线性打表

一次性把 C a 0 C_a^0 Ca0 C a a C_a^a Caa O ( a ⋅ log ⁡ a ) O(a\cdot \log a) O(aloga) 时间内算出。
C a b = C a b − 1 ⋅ a − b + 1 b C_a^b=C_a^{b-1}\cdot \frac{a - b + 1}{b} Cab=Cab1bab+1

二项式定理

( a + b ) n = ∑ i = 0 n ( n i ) a n − i b i (a+b)^{n}=\sum_{i=0}^{n}\left(\begin{array}{l}n \\i\end{array}\right) a^{n-i} b^{i} (a+b)n=i=0n(ni)anibi

多重组合数

k k k 种不一样的球,每种球的个数分别为 n 1 , n 2 , ⋯   , n k n_{1}, n_{2}, \cdots, n_{k} n1,n2,,nk ,且 n = n 1 + n 2 + … + n k n=n_{1}+n_{2}+\ldots+n_{k} n=n1+n2++nk 。这 n n n 个球的全排列数为多重集的排列数,即多重组合数。
( n n 1 , n 2 , ⋯   , n k ) = n ! ∏ i = 1 k n i ! \left(\begin{array}{c}n \\n_{1}, n_{2}, \cdots, n_{k}\end{array}\right)=\frac{n !}{\prod_{i=1}^{k} n_{i} !} (nn1,n2,,nk)=i=1kni!n!
可以看出 ( n m ) \left(\begin{array}{c}n \\m\end{array}\right) (nm) 等价于 ( n m , n − m ) \left(\begin{array}{c}n \\m, n-m\end{array}\right) (nm,nm)


容斥原理

只要满足上式,就可以用下式求 g ( S ) g(S) g(S)
f ( S ) = ∑ T ⊆ S g ( T ) g ( S ) = ∑ T ⊆ S ( − 1 ) ∣ S ∣ − ∣ T ∣ f ( T ) f(S)=\sum_{T \subseteq S} g(T) \\ g(S)=\sum_{T \subseteq S}(-1)^{|S|-|T|} f(T) f(S)=TSg(T)g(S)=TS(1)STf(T)

多项式

拉格朗日插值法

给定n+1个点,确定一个多项式(最高次为n),并将k代入求值

公式

F ( k ) = ∑ i = 0 n y i ∏ i ≠ j k − x j x i − x j F(k) =\sum_{i=0}^n y_i \prod_{i \not= j} \frac{k-x_j}{x_i-x_j} F(k)=i=0nyii=jxixjkxj
涉及除法,所以可能要乘法逆元。

x i x_i xi取值连续时,可以 O ( n ) O(n) O(n)实现。公式化简为
F ( k ) = ∑ i = 0 n y i ∏ i ≠ j k − j i − j F(k) =\sum_{i=0}^n y_i \prod_{i \not= j} \frac{k-j}{i-j} F(k)=i=0nyii=jijkj
p r e i = ∏ j = 0 i ( k − j ) pre_i = \prod_{j=0}^{i} (k-j) prei=j=0i(kj) s u f i = ∏ j = i n ( k − j ) suf_i = \prod_{j=i}^{n} (k-j) sufi=j=in(kj) f a c [ i ] = i ! fac[i] = i! fac[i]=i!,则
F ( k ) = ∑ i = 0 n y i p r e i − 1 s u f i + 1 ( − 1 ) n − i f a c [ i ] f a c [ n − i ] F(k) =\sum_{i=0}^n y_i \frac{pre_{i-1} suf_{i+1}}{(-1)^{n-i}fac[i]fac[n-i]} F(k)=i=0nyi(1)nifac[i]fac[ni]prei1sufi+1
(注意i为0时,特判 p r e i − 1 = 1 pre_{i-1}=1 prei1=1

Problem List

  1. Luo P4781 【模板】拉格朗日插值
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    using ll=long long;
    const ll MOD=998244353;
    const ll MAX=2000+5;
    ll x[MAX],y[MAX];
    ll power(ll a,ll b,ll p)
    {
    	ll ans=1;
    	while(b){
    		if(b&1)ans=ans*a%p;
    		a=a*a%p;
    		b>>=1;
    	}
    	return ans;
    }
    ll calc(ll k,ll n)
    {
    	ll ans=0;
    	for(int i=0;i<n;++i){
    		ll tmp=1;
    		for(int j=0;j<n;++j)if(i!=j)
    			tmp=tmp*(x[i]-x[j]+MOD)%MOD;
    		tmp=power(tmp,MOD-2,MOD);
    		for(int j=0;j<n;++j)if(i!=j)
    			tmp=tmp*(k-x[j]+MOD)%MOD;
    		ans=(ans+tmp*y[i])%MOD; 
    	}
    	return ans;
    }
    int main()
    {
    	ll n,k;
    	scanf("%lld%lld",&n,&k);
    	for(ll i=0;i<n;++i)
    		scanf("%lld%lld",x+i,y+i);
    	printf("%lld\n",calc(k,n));
    }

当x连续时

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);
#define casei int _;for(cin >> _; _; --_)
#define mp make_pair
#define fi first
#define se second
using namespace std;
using ll = long long;
using pii = pair<int, int>;
const ll mod = 998244353;
const int maxn = 160005;
const int maxm = 1e8 + 5;
ll qpow(ll a, ll b){
    ll ans = 1;
    while(b){
        if(b & 1)ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}
ll ni(ll x){
    return qpow(x, mod - 2);
}
ll fac[maxn], y[maxn], pre[maxn], suf[maxn];
void init(){
    fac[0] = fac[1] = 1;
    for(int i = 2; i < maxn; ++ i){
        fac[i] = fac[i - 1] * i % mod;
    }
}
ll f(int n, int k){
    pre[0] = k - 0;
    for(int i=1;i<=n;++i){
        pre[i] = pre[i-1] * (k - i) % mod;
    }
    suf[n + 1] = 1;
    for(int i=n;i>=0;--i){
        suf[i] = suf[i+1] * (k - i) % mod;
    }
    ll ans = 0;
    for(int i = 0; i <= n; ++i){
        ans += y[i] * (i==0 ? 1 : pre[i - 1]) % mod
             * suf[i + 1] % mod
             * ni(fac[i] * fac[n - i] * ((n - i)&1 ? -1 : 1) % mod) % mod;
        ans %= mod;
    }

    return ans;
}
int main(){
    IOS;
    init();
    int n, x;
    cin >> n >> x;
    for(int i=0;i<=n;++i){
        cin >> y[i];
    }
    cout << f(n, x) << endl;
}

FFT

模板

直接调用fft函数,len 必须是 2 k 2^k 2k 形式,on = 1 时是 DFT,on = -1 时是 IDFT。一般先对两个式子进行DFT,再对每项直接求乘积,再对结果进行IDFT。
多项式乘法

#include<bits/stdc++.h>
using namespace std;

const double PI = acos(-1.0);
using Complex = complex<double>;  // real函数无参数时返回实部,有参数时给实部赋值。
/*
 * 进行 FFT 和 IFFT 前的反置变换
 * 位置 i 和 i 的二进制反转后的位置互换
 *len 必须为 2 的幂
 */
void change(Complex y[], int len) {
  int i, j, k;
  for (int i = 1, j = len / 2; i < len - 1; i++) {
    if (i < j) swap(y[i], y[j]);
    // 交换互为小标反转的元素,i<j 保证交换一次
    // i 做正常的 + 1,j 做反转类型的 + 1,始终保持 i 和 j 是反转的
    k = len / 2;
    while (j >= k) {
      j = j - k;
      k = k / 2;
    }
    if (j < k) j += k;
  }
}
/*
 * 做 FFT
 *len 必须是 2^k 形式
 *on == 1 时是 DFT,on == -1 时是 IDFT
 */
void fft(Complex y[], int len, int on) {
  change(y, len);
  for (int h = 2; h <= len; h <<= 1) {
    Complex wn(cos(2 * PI / h), sin(on * 2 * PI / h));
    for (int j = 0; j < len; j += h) {
      Complex w(1, 0);
      for (int k = j; k < j + h / 2; k++) {
        Complex u = y[k];
        Complex t = w * y[k + h / 2];
        y[k] = u + t;
        y[k + h / 2] = u - t;
        w = w * wn;
      }
    }
  }
  if (on == -1) {
    for (int i = 0; i < len; i++) {
      y[i].real(y[i].real() / len);
    }
  }
}

const int MAXN = int(1e6+5)<<2; // 和线段树一样,要开四倍空间
Complex x1[MAXN], x2[MAXN];
char str1[MAXN / 2], str2[MAXN / 2];
int sum[MAXN];

int main() {
    int  n, m, len;
    cin >> n >> m;
    for(int v,i=0;i<=n;++i){
        cin >> v;
        x1[i] = Complex(v, 0);
    }
    for(int v,i=0;i<=m;++i){
        cin >> v;
        x2[i] = Complex(v, 0);
    }
    len = 1;
    while(len < (n+1)*2 || len < (m+1)*2)len <<= 1;
    fft(x1, len, 1);
    fft(x2, len, 1);
    for(int i = 0; i < len; ++i)
        x1[i] = x1[i] * x2[i];
    fft(x1, len, -1);
    len = n + m + 1;
    cout << int(x1[0].real() + 0.5);
    for(int i = 1; i < len; ++i){
        cout << ' ' << int(x1[i].real() + 0.5);
    }
    cout << endl;
 	return 0;
}

常见应用

  1. 多项式乘法或大整数乘法。如果做大整数乘法记得需要进位。
    F ( x ) = G ( x ) ∗ H ( x ) , G ( x ) = ∑ i = 0 n a i ⋅ x i ,   H ( x ) = ∑ i = 0 m b i ⋅ x i F(x) = G(x)*H(x),\\ G(x)=\sum_{i=0}^n{a_i\cdot x_i},\ H(x)=\sum_{i=0}^m{b_i\cdot x_i} F(x)=G(x)H(x),G(x)=i=0naixi, H(x)=i=0mbixi
    大整数乘法:
#include<bits/stdc++.h>
using namespace std;

const double PI = acos(-1.0);
using Complex = complex<double>;  // real函数无参数时返回实部,有参数时给实部赋值。
/*
 * 进行 FFT 和 IFFT 前的反置变换
 * 位置 i 和 i 的二进制反转后的位置互换
 *len 必须为 2 的幂
 */
void change(Complex y[], int len) {
  int i, j, k;
  for (int i = 1, j = len / 2; i < len - 1; i++) {
    if (i < j) swap(y[i], y[j]);
    // 交换互为小标反转的元素,i<j 保证交换一次
    // i 做正常的 + 1,j 做反转类型的 + 1,始终保持 i 和 j 是反转的
    k = len / 2;
    while (j >= k) {
      j = j - k;
      k = k / 2;
    }
    if (j < k) j += k;
  }
}
/*
 * 做 FFT
 *len 必须是 2^k 形式
 *on == 1 时是 DFT,on == -1 时是 IDFT
 */
void fft(Complex y[], int len, int on) {
  change(y, len);
  for (int h = 2; h <= len; h <<= 1) {
    Complex wn(cos(2 * PI / h), sin(on * 2 * PI / h));
    for (int j = 0; j < len; j += h) {
      Complex w(1, 0);
      for (int k = j; k < j + h / 2; k++) {
        Complex u = y[k];
        Complex t = w * y[k + h / 2];
        y[k] = u + t;
        y[k + h / 2] = u - t;
        w = w * wn;
      }
    }
  }
  if (on == -1) {
    for (int i = 0; i < len; i++) {
      y[i].real(y[i].real() / len);
    }
  }
}

const int MAXN = (1e6+5)<<2; // 和线段树一样,要开四倍空间
Complex x1[MAXN], x2[MAXN];
char str1[MAXN / 2], str2[MAXN / 2];
int sum[MAXN];

int main() {
  while (scanf("%s%s", str1, str2) == 2) {
    int len1 = strlen(str1);
    int len2 = strlen(str2);
    int len = 1;
    while (len < len1 * 2 || len < len2 * 2) len <<= 1;
    for (int i = 0; i < len1; i++) x1[i] = Complex(str1[len1 - 1 - i] - '0', 0);
    for (int i = len1; i < len; i++) x1[i] = Complex(0, 0);
    for (int i = 0; i < len2; i++) x2[i] = Complex(str2[len2 - 1 - i] - '0', 0);
    for (int i = len2; i < len; i++) x2[i] = Complex(0, 0);
    fft(x1, len, 1);
    fft(x2, len, 1);
    for (int i = 0; i < len; i++) x1[i] = x1[i] * x2[i];
    fft(x1, len, -1);
    for (int i = 0; i < len; i++) sum[i] = int(x1[i].real() + 0.5);
    for (int i = 0; i < len; i++) {
      sum[i + 1] += sum[i] / 10;
      sum[i] %= 10;
    }
    len = len1 + len2 - 1;
    while (sum[len] == 0 && len > 0) len--;
    for (int i = len; i >= 0; i--) printf("%c", sum[i] + '0');
    printf("\n");
  }
  return 0;
}
  1. 卷积
    求诸如以下式子,因为多项式乘法,即幂次的加法。
    s u m n = ∑ i + j = n a i ⋅ b j sum_{n} = \sum_{i+j=n}{a_i\cdot b_j} sumn=i+j=naibj
    字符串匹配类的题可以做,组合数学类的题也可以做。比如下式(Red-White Fence),快速求出对于所有k的情况就可以 O ( n ⋅ log ⁡ n ) O(n\cdot \log n) O(nlogn)复杂度解决。
    s u m k = ∑ i = 0 k C 2 ∗ m k − i ⋅ C n i 2 i sum_k = \sum_{i=0}^{k}{C_{2*m}^{k-i}\cdot C_n^i2^i} sumk=i=0kC2mkiCni2i
    s u m k = ∑ i = 0 n a i ⋅ b k − i , k ≤ n sum_{k} = \sum_{i=0}^n{a_i\cdot b_{k-i}}, k \le n sumk=i=0naibki,kn时,由于是 0 ⋯ n 0\cdots n 0n 累加,不是 0 ⋯ k 0\cdots k 0k,所以应该把 b j b_j bj向右平移 n n n。即令 c n + j = b j , a n + 1 ⋯ 2 n = 0 c_{n+j} = b_j, a_{n+1\cdots 2n}=0 cn+j=bj,an+12n=0 。最后卷积后第 n + k n + k n+k项目就是 s u m k sum_k sumk 。(P5667 拉格朗日插值2
    s u m k = ∑ i = 0 n a i ⋅ b k − i = ∑ i = 0 n a i ⋅ c n + k − i = ∑ i = 0 n + k a i ⋅ c n + k − i sum_{k} = \sum_{i=0}^n{a_i\cdot b_{k-i}} = \sum_{i=0}^n{a_i\cdot c_{n+k-i}}=\sum_{i=0}^{n+k}{a_i\cdot c_{n+k-i}} sumk=i=0naibki=i=0naicn+ki=i=0n+kaicn+ki
    CF528D
    求S串有多少个子串与T串匹配,两个字母匹配指周围 2 k 2k 2k个位置有相同的字母。
    思路:把4个字母分开判断, a i a_i ai表示 i i i位置前后是否有对应字母。对于每个起始位置 k k k,求下式。注意起始位置为 k k k 的卷积结果应该为 s u m m + k − 1 sum_{m+k-1} summ+k1,因为 i + j = m + k − 1 i + j = m + k - 1 i+j=m+k1
    s u m m + k − 1 = ∑ i = 0 m − 1 a k + i ⋅ b i = ∑ i + j = m + k − 1 a k + i ⋅ c j , b i = c m − i − 1 sum_{m+k-1} = \sum_{i=0}^{m-1}{a_{k+i}\cdot b_i}=\sum_{i+j=m+k-1}{a_{k+i}\cdot c_j},\\ b_i=c_{m-i-1} summ+k1=i=0m1ak+ibi=i+j=m+k1ak+icj,bi=cmi1
#include <bits/stdc++.h>
using namespace std;
const double PI = acos(-1.0);
using Complex = complex<double>;

void change(Complex y[], int len){
    int i, j, k;
    for(int i = 1, j = len / 2; i < len - 1; ++i){
        if(i < j) swap(y[i], y[j]);
        k = len / 2;
        while(j >= k){
            j = j - k;
            k = k / 2;
        }
        if(j < k) j += k;
    }
}
void fft(Complex y[], int len, int on){
    change(y, len);
    for(int h = 2; h <= len; h <<= 1){
        Complex wn(cos(2 * PI / h), sin(on * 2 * PI / h));
        for(int j = 0; j < len; j += h){
            Complex w(1, 0);
            for(int k = j; k < j + h / 2; ++k){
                Complex u = y[k];
                Complex t = w * y[k + h / 2];
                y[k] = u + t;
                y[k + h / 2] = u - t;
                w = w * wn;
            }
        }
    }
    if(on == -1){
        for(int i = 0; i < len; ++i){
            y[i].real(y[i].real() / len);
        }
    }
}

const int maxn = int(2e5+5) << 2;
Complex x1[maxn], x2[maxn];
char s[maxn], t[maxn];
char chars[] = "AGCT";
int sum[maxn];

int main(){
    int n, m, len = 1, k;
    cin >> n >> m >> k >> s >> t;
    while(len < n * 2 || len < m * 2)len <<= 1;
    for(int ch = 0; ch < 4; ++ch){
        int cnt = 0;
        for(int i = 0; i < min(k, n); ++i)
            if(s[i] == chars[ch])
                ++ cnt;
        for(int i = 0; i < n; ++i){
            if(i + k < n && s[i + k] == chars[ch])
                ++ cnt;
            x1[i] = Complex(bool(cnt), 0);
            if(i - k >= 0 && s[i - k] == chars[ch])
                -- cnt;
        }
        for(int i = n; i < len; ++i)
            x1[i] = Complex(0, 0);
        for(int i = 0; i < m; ++i){
            x2[m - i -1] = Complex(t[i]==chars[ch], 0); // 注意逆序
        }
        for(int i = m; i < len; ++i)
            x2[i] = Complex(0, 0);
        fft(x1, len, 1);
        fft(x2, len, 1);
        for(int i = 0; i < len; ++i)
            x1[i] = x1[i] * x2[i];
        fft(x1, len, -1);
        for(int i = m - 1; i < n; ++i){
            sum[i] += int(x1[i].real() + 0.5);
        }
    }
    int ans = 0;
    for(int i = m - 1; i < n; ++i){
        if(sum[i] == m)++ans;
    }
    cout << ans << endl;
}

NTT

和FFT一样,把复跟换成原根。时间上大概快十分之一。

  1. ntt之前要先算出r数组
  2. 相乘时要取模。

有固定的模数和原根搭配。(模数不能过小,不能表示所有的卷积和)

模数原根
9982443533
73400333
78643310
409613

模板

#include<bits/stdc++.h>
using namespace std;
const int N = int(1e6 + 5) << 2, P = 998244353;

inline int qpow(int x, int y) {
  int res(1);
  while (y) {
    if (y & 1) res = 1ll * res * x % P;
    x = 1ll * x * x % P;
    y >>= 1;
  }
  return res;
}

int r[N];

void ntt(int y[], int len, int opt) {
    int i, j, k, m, gn, g, tmp;
    for (i = 0; i < len; ++i)
        if (r[i] < i) swap(y[i], y[r[i]]);
    for (m = 2; m <= len; m <<= 1) {
        k = m >> 1;
        gn = qpow(3, (P - 1) / m);
        for (i = 0; i < len; i += m) {
            g = 1;
            for (j = 0; j < k; ++j, g = 1ll * g * gn % P) {
                tmp = 1ll * y[i + j + k] * g % P;
                y[i + j + k] = (y[i + j] - tmp + P) % P;
                y[i + j] = (y[i + j] + tmp) % P;
            }
        }
    }
    if (opt == -1) {
        reverse(y + 1, y + len);
        int inv = qpow(len, P - 2);
        for (i = 0; i < len; ++i)
            y[i] = 1ll * y[i] * inv % P;
    }
}

int x1[N], x2[N], sum[N];
char s1[N], s2[N];


int main() {
    while(cin >> s1 >> s2){
        int len = 1, n, m;
        n = strlen(s1);
        m = strlen(s2);
        for(int i = 0; i < n; ++i)
            x1[n - i - 1] = s1[i] - '0';
        for(int i = 0; i < m; ++i)
            x2[m - i - 1] = s2[i] - '0';
/* -----------------------------------------   */
        while(len < 2 * n || len < 2 * m)len <<= 1;
        for(int i = 0; i < len; ++i) // ntt前要r[]进行初始化
            r[i] = (i & 1) * (len >> 1) + (r[i >> 1] >> 1);
        ntt(x1, len, 1);
        ntt(x2, len, 1);
        for(int i = 0; i < len; ++i)
            sum[i] = 1ll * x1[i] * x2[i] % P;  // 这里比fft多一个取模
        ntt(sum, len, -1);
/* -----------------------------------------   */
        for(int i = 0; i < len; ++i){
            sum[i + 1] += sum[i] / 10;
            sum[i] %= 10;
        }
        while(len > 1 && sum[len - 1] == 0) --len;
        for(int i = len - 1; i >= 0; --i)
            cout << sum[i];
        cout << endl;
    }
    return 0;
}

应用

CF528D

#include <bits/stdc++.h>
using namespace std;
const int maxn = int(2e5+5) << 2, P = 998244353;
int x1[maxn], x2[maxn];
char s[maxn], t[maxn];
char chars[] = "AGCT";
int sum[maxn];
int r[maxn];
inline int qpow(int x, int y) {
  int res(1);
  while (y) {
    if (y & 1) res = 1ll * res * x % P;
    x = 1ll * x * x % P;
    y >>= 1;
  }
  return res;
}


void ntt(int y[], int len, int opt) {
    int i, j, k, m, gn, g, tmp;
    for (i = 0; i < len; ++i)
        if (r[i] < i) swap(y[i], y[r[i]]);
    for (m = 2; m <= len; m <<= 1) {
        k = m >> 1;
        gn = qpow(3, (P - 1) / m);
        for (i = 0; i < len; i += m) {
            g = 1;
            for (j = 0; j < k; ++j, g = 1ll * g * gn % P) {
                tmp = 1ll * y[i + j + k] * g % P;
                y[i + j + k] = (y[i + j] - tmp + P) % P;
                y[i + j] = (y[i + j] + tmp) % P;
            }
        }
    }
    if (opt == -1) {
        reverse(y + 1, y + len);
        int inv = qpow(len, P - 2);
        for (i = 0; i < len; ++i)
            y[i] = 1ll * y[i] * inv % P;
    }
}


int main(){
    int n, m, len = 1, k;
    cin >> n >> m >> k >> s >> t;
    while(len < n * 2 || len < m * 2)len <<= 1;
    for(int ch = 0; ch < 4; ++ch){
        int cnt = 0;
        for(int i = 0; i < min(k, n); ++i)
            if(s[i] == chars[ch])
                ++ cnt;
        for(int i = 0; i < n; ++i){
            if(i + k < n && s[i + k] == chars[ch])
                ++ cnt;
            x1[i] = bool(cnt);
            if(i - k >= 0 && s[i - k] == chars[ch])
                -- cnt;
        }
        for(int i = n; i < len; ++i)
            x1[i] = 0;
        for(int i = 0; i < m; ++i){
            x2[m - i -1] = t[i]==chars[ch];
        }
        for(int i = m; i < len; ++i)
            x2[i] = 0;
        for(int i = 0; i < len; ++i) // ntt前要r[]进行初始化
            r[i] = (i & 1) * (len >> 1) + (r[i >> 1] >> 1);
        ntt(x1, len, 1);
        ntt(x2, len, 1);
        for(int i = 0; i < len; ++i)
            x1[i] = 1ll * x1[i] * x2[i] % P;
        ntt(x1, len, -1);
        for(int i = m - 1; i < n; ++i){
            //cout << x1[i] << " ";
            sum[i] += x1[i];
        }//cout << endl;
    }
    int ans = 0;
    for(int i = m - 1; i < n; ++i){
        //cout << sum[i] << " ";
        if(sum[i] == m)++ans;
    }
    cout << ans << endl;
}

生成函数

普通生成函数求组合数

序列 a a a 的普通生成函数(ordinary generating function,OGF)定义为形式幂级数:
F ( x ) = ∑ n a n x n F(x)=\sum_{n} a_{n} x^{n} F(x)=nanxn
封闭形式,就是把幂次形式变成简单的形式,更好的做运算。
常见的封闭形式化简方法
F ( x ) = ∑ i = 0 n a i x i x ⋅ F ( x ) = ∑ i = 1 n + 1 a i x i F ( x ) = 1 + x ⋅ F ( x ) − a n + 1 ⋅ x n + 1 F ( x ) = 1 − a n + 1 ⋅ x n + 1 1 − x F(x)=\sum_{i=0}^{n} a_{i} x^{i}\\ x \cdot F(x)=\sum_{i=1}^{n+1} a_{i} x^{i}\\ F(x) = 1 + x \cdot F(x) - a_{n+1}\cdot x^{n+1}\\ F(x) = \frac{1 - a_{n+1}\cdot x^{n+1}}{1-x} F(x)=i=0naixixF(x)=i=1n+1aixiF(x)=1+xF(x)an+1xn+1F(x)=1x1an+1xn+1
重要的封闭形式:等比数列的封闭形式,通过它可以把其他数列的封闭形式化简,得到等比数列的加权累加(比如斐波那契数列)。
F ( x ) = ∑ n ≥ 0 p n x n = 1 1 − p x F(x)=\sum_{n \geq 0} p^{n} x^{n} =\frac{1}{1-p x} F(x)=n0pnxn=1px1
常见的数列封闭形式
在这里插入图片描述
在这里插入图片描述

牛顿二项式定理

二项式定理的扩展。当 α ∈ C \alpha \in C αC,即复数范围内都可以用。
( 1 + x ) α = Σ r = 0 ∞ ( α r ) x r ( α r ) = α ( α − 1 ) … ( α − r + 1 ) r ! (1+x)^{\alpha}=\Sigma_{r=0}^{\infty}\left(\begin{array}{l}\alpha \\r\end{array}\right) x^{r}\\ \left(\begin{array}{l}\alpha \\r\end{array}\right)=\frac{\alpha(\alpha-1) \ldots(\alpha-r+1)}{r !} (1+x)α=Σr=0(αr)xr(αr)=r!α(α1)(αr+1)

有限个

HDU 2082
题意:a ~ z的权值分别为1 ~ 26。给出每个字母的数量,求权值≤50的字母组合数。
直接把不同字母的生成函数相乘就行,模板题。若项数多,可以用FFT进行多项式乘法。
如只有2个a,3个c,1个g
( x 0 + x 1 + x 2 ) ( x 0 + x 3 + x 6 + x 9 ) ( x 7 ) (x^0+x^1+x^2)(x^0+x^3+x^6+x^9)(x^7) (x0+x1+x2)(x0+x3+x6+x9)(x7)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int num[30];  // 每个字母的数量
ll sup[55];
ll tmp[55];
int main(){
    int T;
    cin>>T;
    while(T--){
        for(int i=1;i<=26;++i){
            cin>>num[i];
            if(num[i]*i>50)num[i]=50/i;
        }
        memset(tmp,0,sizeof(tmp));
        memset(sup,0,sizeof(sup));
        sup[0] = 1;
        for(int i=1;i<=26;++i){
            for(int j=0;j<=num[i];++j){
                for(int k=0;j*i+k<=50;++k){
                    tmp[j*i+k] += sup[k];
                }
            }
            for(int j=0;j<=50;++j){
                sup[j] = tmp[j];
                tmp[j] = 0;
            }
        }
        ll ans = 0;
        for(int i=1;i<=50;++i)
            ans += sup[i];
        cout<<ans<<endl;
    }
}

无限个

#3028. 食物
题意:求满足食物数量为以下条件的 n n n 个食品的不同组合数。

承德汉堡:偶数个可乐:0或1个鸡腿:0、1或2个蜜桃多:奇数个鸡块:4的倍数个包子:0,1,2或3个土豆片炒肉:0或1个面包:3的倍数个

思路:每个食物都可以写出普通生成函数,然后答案就是生成函数相乘(卷积)后的 x n x^n xn 的系数。
#3027. [Ceoi2004]Sweet
题意:n种糖果,每种糖果有 m i m_i mi 个。问取 [ a , b ] [a,b] [a,b] 个糖果,有多少种取法。
思路:显然,第 i i i 种糖果的生成函数的封闭形式为 1 − x m i + 1 1 − x \frac{1-x^{m_i + 1}}{1-x} 1x1xmi+1。则所有相乘后的
G ( x ) = ( 1 − x ) − n ∏ i = 1 n 1 − x m i + 1 G(x) = (1-x)^{-n}\prod_{i=1}^n{1-x^{m_i + 1}} G(x)=(1x)ni=1n1xmi+1
负次幂通过牛顿二项式定理得到
( 1 − x ) − n = ∑ i ≥ 0 ( − n i ) ( − x ) i = ∑ i ≥ 0 ( n − 1 + i i ) x i \begin{aligned}(1-x)^{-n} &=\sum_{i \geq 0}\left(\begin{array}{c}-n \\i\end{array}\right)(-x)^{i} \\&=\sum_{i \geq 0}\left(\begin{array}{c}n-1+i \\i\end{array}\right) x^{i}\end{aligned} (1x)n=i0(ni)(x)i=i0(n1+ii)xi
然后就可以通过多项式乘法得到 ∏ i = 1 n 1 − x m i + 1 = ∑ c k ⋅ x k \prod_{i=1}^n{1-x^{m_i + 1}} = \sum{c_k \cdot x^k} i=1n1xmi+1=ckxk 。然后枚举 c k c_k ck就在 ( 1 − x ) − n (1-x)^{-n} (1x)n 中求 x a − k x^{a-k} xak 的系数 + ⋯ + + \cdots + ++ x b − k x^{b-k} xbk的系数。可以通过常用组合公式 ∑ l = 0 n ( l k ) = ( n + 1 k + 1 ) \sum_{l=0}^{n}\left(\begin{array}{l}l \\k\end{array}\right)=\left(\begin{array}{l}n+1 \\k+1\end{array}\right) l=0n(lk)=(n+1k+1)
得到。

数论

乘法逆元

  1. O ( n ) O(n) O(n) 1 … n 1\dots n 1n 的逆元
inv[1] = 1;
for(int i = 2; i <= n; ++i){
    inv[i] = (mod - mod / i) * inv[mod % i] % mod;
}
  1. O ( n ) O(n) O(n) a 1 ⋯ a n a_1\cdots a_n a1an 的逆元。用 s u m [ i ] sum[i] sum[i] 表示前缀乘, i n v s u m invsum invsum表示前缀乘的逆元。
sum[0] =1;
for(int i = 1; i <= n; ++i){
    read(a[i]);
    sum[i] = sum[i - 1] * a[i] % p;
}
invsum = qpow(sum[n], p - 2, p);
inv[n] = invsum * sum[n - 1] % p;
for(int i = n - 1; i; --i){
    invsum = invsum * a[i + 1] % p;
    inv[i] = invsum * sum[i - 1] % p;
}

线性代数

高斯消元

该模板可适用于浮点数或 mod 某数。

// 共有n个等式,每个等式有n个变量,a[i][n]为第i行的右式常数b
for(int i=0;i<n;i++){
	// 在i ~ n-1行中找a[pos][i]最大的一行
	int pos = i;
	for(int j=i;j<n;j++)
		if(fabs(a[j][i]) > fabs(a[i][i]))
			pos=j;
	// 把那一行交换到第i行
	for(int j=0;j<=n;j++) swap(a[i][j], a[pos][j]);
	double tmp = a[i][i];
	if(fabs(tmp) < EPS){
	// 如果为0的话,证明第i个变量是个可随意变化的量或者方程组无解
		cout<<"No Solution"<<endl;
		return 0;
	}
	for(int j=i;j<=n;j++) a[i][j] /= tmp; // 把这一行根据a[i][i]单位化
	for(int k=0;k<n;k++)if(k!=i){ // 把每行的第i列清零
		tmp=a[k][i];
		for(int j=i;j<=n;j++){
			a[k][j]-=tmp*a[i][j];
		}
	}
}

高斯消元,并判断无解和无穷解的情况

int goss(int n){
    int r = 0;
    for(int c = 0; c < n; ++c){
        int pos = r;
        double mx = fabs(a[r][c]);
        for(int i = r + 1; i < n; ++i)
            if(fabs(a[i][c]) > mx)
                pos = i, mx = fabs(a[i][c]);
        if(mx < eps)continue; // jump over this column
        for(int j = 0; j <= n; ++j) swap(a[pos][j], a[r][j]);
        double tmp = a[r][c];
        for(int j = c; j <= n; ++j) a[r][j] /= tmp; // unitize this line
        for(int i = 0; i < n; ++i){
            if(i == r)continue;
            tmp = a[i][c];
            for(int j = c; j <= n; ++j)
                a[i][j] -= tmp * a[r][j];
        }
        r++;
    }
    if(r < n){
        for(int i = r; i < n; ++i)
            if(fabs(a[i][n]) > eps)return -1; // no solution
        return 0;  // inf solution
    }
    return 1; // only one solution
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值