文章目录
组合数学
组合数
常用公式
(
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(n−1k−1)
(
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)=(n−1m)+(n−1m−1)
(
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=0∑n(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=0∑n(−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=0∑m(ni)(mm−i)=(m+nm)(n≥m)
∑
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=0∑n(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=0∑ni(ni)=n2n−1
∑
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=0∑ni2(ni)=n(n+1)2n−2
∑
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=0∑n(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)(n−kr−k)
∑
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=0∑n(n−ii)=Fn+1
求组合数
杨辉三角
O
(
n
2
)
O(n^2)
O(n2)求出
C
(
0
⋯
n
,
0
⋯
n
)
C(0\cdots n, 0 \cdots n)
C(0⋯n,0⋯n)
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(n−1,k)+C(n−1,k−1)
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(a⋅loga) 时间内算出。
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=Cab−1⋅ba−b+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=0∑n(ni)an−ibi
多重组合数
有
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,n−m) 。
容斥原理
只要满足上式,就可以用下式求
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)=T⊆S∑g(T)g(S)=T⊆S∑(−1)∣S∣−∣T∣f(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=0∑nyii=j∏xi−xjk−xj
涉及除法,所以可能要乘法逆元。
当
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=0∑nyii=j∏i−jk−j
设
p
r
e
i
=
∏
j
=
0
i
(
k
−
j
)
pre_i = \prod_{j=0}^{i} (k-j)
prei=∏j=0i(k−j),
s
u
f
i
=
∏
j
=
i
n
(
k
−
j
)
suf_i = \prod_{j=i}^{n} (k-j)
sufi=∏j=in(k−j),
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=0∑nyi(−1)n−ifac[i]fac[n−i]prei−1sufi+1
(注意i为0时,特判
p
r
e
i
−
1
=
1
pre_{i-1}=1
prei−1=1)
Problem List
#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;
}
常见应用
- 多项式乘法或大整数乘法。如果做大整数乘法记得需要进位。
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=0∑nai⋅xi, H(x)=i=0∑mbi⋅xi
大整数乘法:
#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;
}
- 卷积
求诸如以下式子,因为多项式乘法,即幂次的加法。
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=n∑ai⋅bj
字符串匹配类的题可以做,组合数学类的题也可以做。比如下式(Red-White Fence),快速求出对于所有k的情况就可以 O ( n ⋅ log n ) O(n\cdot \log n) O(n⋅logn)复杂度解决。
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=0∑kC2∗mk−i⋅Cni2i
当 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=0nai⋅bk−i,k≤n时,由于是 0 ⋯ n 0\cdots n 0⋯n 累加,不是 0 ⋯ k 0\cdots k 0⋯k,所以应该把 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+1⋯2n=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=0∑nai⋅bk−i=i=0∑nai⋅cn+k−i=i=0∑n+kai⋅cn+k−i
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+k−1,因为 i + j = m + k − 1 i + j = m + k - 1 i+j=m+k−1。
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+k−1=i=0∑m−1ak+i⋅bi=i+j=m+k−1∑ak+i⋅cj,bi=cm−i−1
#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一样,把复跟换成原根。时间上大概快十分之一。
- ntt之前要先算出r数组
- 相乘时要取模。
有固定的模数和原根搭配。(模数不能过小,不能表示所有的卷积和)
模数 | 原根 |
---|---|
998244353 | 3 |
7340033 | 3 |
786433 | 10 |
40961 | 3 |
模板
#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;
}
应用
#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)=n∑anxn
封闭形式,就是把幂次形式变成简单的形式,更好的做运算。
常见的封闭形式化简方法:
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=0∑naixix⋅F(x)=i=1∑n+1aixiF(x)=1+x⋅F(x)−an+1⋅xn+1F(x)=1−x1−an+1⋅xn+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)=n≥0∑pnxn=1−px1
常见的数列封闭形式:
牛顿二项式定理
二项式定理的扩展。当
α
∈
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}
1−x1−xmi+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)=(1−x)−ni=1∏n1−xmi+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}
(1−x)−n=i≥0∑(−ni)(−x)i=i≥0∑(n−1+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=1n1−xmi+1=∑ck⋅xk 。然后枚举
c
k
c_k
ck就在
(
1
−
x
)
−
n
(1-x)^{-n}
(1−x)−n 中求
x
a
−
k
x^{a-k}
xa−k 的系数
+
⋯
+
+ \cdots +
+⋯+
x
b
−
k
x^{b-k}
xb−k的系数。可以通过常用组合公式
∑
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=0∑n(lk)=(n+1k+1)
得到。
数论
乘法逆元
- O ( n ) O(n) O(n) 求 1 … n 1\dots n 1…n 的逆元
inv[1] = 1;
for(int i = 2; i <= n; ++i){
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
}
- O ( n ) O(n) O(n) 求 a 1 ⋯ a n a_1\cdots a_n a1⋯an 的逆元。用 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
}