簡單數學
常見概念和符號
數論常見符號
-
整除符號 \(x\mid y\),表示 \(x\) 整除 \(y\) ,即 \(x\) 是 \(y\) 的因數
-
取模符號 \(x \bmod y\) ,表示 \(x\) 除以 \(y\) 以后的余數
-
互質符號 \(x~\bot~y\) ,表示 \(x\) ,\(y\) 互質
-
最大公約數 \(\gcd(x,y)\)
-
最小公倍數 \(\text{lcm} (x,y)\)
-
求和符號:\(\sum\) 符號,表示滿足特定條件的數的和
\(\sum _{i=1} ^{n} ai\) 表示 \(a_1\sim a_n\) 的和
\(\sum_{S\subseteq T} |S|\)
-
求積符號:\(\Pi\) 符號,表示滿足特定條件的數的積
\(\Pi_{i=1}^n\) 表示 \(n!\)
\(\Pi _{i=1}^{n} a_i\) 表示從 \(a_1\sim a_n\) 的積
\(\Pi _{x\mid d} x\) 表示 \(d\) 的所有因數的乘積 -
整除符號:\(\mid\) ,\(a \mid b\) 表示 \(a\) 是 \(b\) 的約數 \(b\) 不被 \(a\) 整除記作 \(a \nmid b\) 。
-
同余符號:\(\equiv\) ,\(a\equiv b \pmod c\) 表示 \(\dfrac a c -\lfloor \dfrac a c \rfloor=\dfrac b c-\lfloor \dfrac b c \rfloor\) 或者說 \(a/c\) 和 \(b/c\) 的余數相同
-
卷積符號:
*
卷積分很多種,常見的有
加法卷積:\[ h=f*g=\sum_{i+j==n} f(i)g(j) \]乘法卷積(狄利克雷卷積)(滿足交換律和結合律):
\[ h=f*g=\sum_{d\mid n}f(d)g(\dfrac n d) \]位運算卷積:
\[ h=f*g=\sum_{i\oplus j==n} f(i)g(j) \]
數論函數
數論函數指定義域為正整數的函數。數論函數也可以視作一個數列。
恆等函數
\(I(x)=1(x)= 1\)
元函數
又稱單位元
\(\varepsilon(n)=[n==1]\)
markdown格式為$\varepsilon$
單位函數
\(id_k(n)=n^k\),\(id_1(n)\) 通常簡記為 \(id(n)=n\)
積性函數
若 \(f(n)\) 表示 \(f(1)=1\) 且 \(\forall x,y \in N_+\) \(\gcd(x,y)=1\) 都有 \(f(xy)=f(x)f(y)\),則 \(f(n)\) 為積性函數
若 \(f(n)\) 表示 \(f(1)=1\) 且 \(\forall x,y \in N_+\) 都有 \(f(xy)=f(x)f(y)\),則 \(f(n)\) 為完全積性函數
歐拉函數
\(φ(n)\) :定義:1~n中與n互質的數的個數
公式:
莫比烏斯函數
\(k_i\) 表示質因數分解后每一個質因子的指數
我們一般表示為 \(\mu(x)\)
- \(\exists k_i\ge 2\),\(\mu(x)=0\)
- \(\forall k_i=1\),\(\mu(x)=(-1)^y\) (\(y\) 是質因數分解后有幾項)
- \(x=1\) ,\(\mu(x)=1\)
如:
\(\mu(6)=1\) \(\mu(7)=-1\) \(\mu(8)=0\)
設 \(s(n)=\sum_{i=d\mid n}^{n} \mu (i)\)
如 \(s(6) = \mu(1) +\mu(2) +\mu(3) +\mu(6)\)
那么:
- 當 \(n=1\),\(s(n)=1\)
- 當 \(n>1\),\(s(n)=0\)
對於2
的證明:
首先我們對於 \(n\) 因式分解:\(n=p_1^{\alpha_1}\times p_2^{\alpha_2}\times\dots\times p_k^{\alpha_k}\) (\(k\ge 1\))
那么我們的每一個 \(d\) 都有:\(d=p_1^{\beta_1}\times p_2^{\beta_2}\times\dots\times p_k^{\beta_k}\) (\(\forall i \in [1,k]\) \(0\le\beta_i\le \alpha_i\))
那么我們算一下 \(\mu(d)\) 可知,我們只需要考慮,如果\(\exists i \in [1,k]\),\(\beta_i\ge 2\) 則原式為 \(0\)
所以我們只需要看里面有多少個1即可
所以我們用組合數算一下:
我們這時候看一下二項式定理:
欽定 \(a=1\) \(b=-1\)
所以 \(s(x)\) 必然等於 \(=0\),得證
約數個數函數
\(d(x)=\sum_{d\mid n}1\),不用多說了,就是約數個數
卷積公式
\(\varepsilon*F=F\),\(F\) 是任意數論函數
證明:\(\varepsilon*F=\sum_{d\mid n}\varepsilon(\dfrac n d)F(d)\)
只有當 \(n = d\) 時,\(\dfrac n d\) 為1,此時的值就是 \(F(n)\),所以得證
\(\varepsilon=\mu * 1\),即 \([n=1]=\sum_{d\mid n}\mu(d)\),使得所有 \(1\) 的限制都變成了可以被預處理的 \(\mu(d)\)
證明:由於 \(\mu * 1=\sum_{d\mid n}\mu(d)\),前面我們已經證明了當 \(n\not =1\) 的時候原式值為 \(0\),當 \(n=1\) 時值為 \(1\),所以得證
\(id=\varphi*1\),即 \(n=\sum_{d\mid n}\varphi(d)\)
\(\mu * id=\varphi\),由前面幾個合起來就有了
\(\mu * d=1\),可以據此進行反演
導數公式
\((C)^{'}=0\) \((x^\mu)'=\mu x^{\mu-1}\)
\((a^x)'=a^x\ln x\) ( \(a\) 為常數) \((\sin x)'=\cos x\)
\((\cos x)'=-\sin x\) \((\tan x)'=\sec^2x\)
\((\cot x)'=-\csc^2x\) \((\sec x)'=\sec x \cot x\)
\((\csc x)'=-\csc x\cot x\) \((\ln x)'=\dfrac{1}{x}\)
\(({\log_a}^x)'=\dfrac{1}{x\ln a}\) \((e^x)'=e^x\)
加減公式:
\((u\pm v)'=u'\pm v'\) \((Cu)'=Cu'\) (C是常數)
\((uv)'=u'v+uv'\) \((\dfrac{u}{v})'=(\dfrac{u'v-uv'}{v^2})\)
復數
設\(a,b\)為實數,\(i^2=-1\),形如\(a+bi\)的數叫復數,其中\(i\)被稱為虛數單位,復數域是目前已知最大的域
在復平面中,\(x\) 代表實數,\(y\) 軸(除原點外的點)代表虛數,從原點\((0,0)\)到\((a,b)\)的向量表示復數\(a+bi\)
模長:從原點\((0,0)\)到點\((a,b)\)的距離,即\(\sqrt{a^2+b^2}\)
幅角:假設以逆時針為正方向,從xx軸正半軸到已知向量的轉角的有向角叫做幅角
計算:平行四邊形法則(其實就是分配律),注意這里的\(i^2\)為-1:
幾何定義:復數相乘,模長相乘,幅角相加(至今我也沒看懂)
\((a+bi)\times (c+di)=ac+bdi^2+bci+adi=ac-bd+(bc+ad)i\)
同時,我們由向量的知識遷移到復數上來,定義 復數的模 就是復數所對應的向量的模。
復數 \(z=a+bi\) 的模 \(|z|=\sqrt{a^2+b^2}\)
於是為了方便,我們常把復數 \(z=a+bi\) 稱為點 \(Z\)
由向量的知識我們發現,虛數不可以比較大小
復數滿足交換律,結合律,對加法的分配律
當兩個虛數實部相等,虛部互為相反數時,這兩個復數互為 共軛復數。
即 \(z=a+bi\) 的共軛復數為 \(z=a-bi\)
CZY數學三大定理
CZY數學三大公理指的是以下三大公理:二是質數定理、大於二質數構造公理、質數自反性公理。
三大定理介紹
「二是質數定理」
內容:定義 \(2\) 是質數
證明:反證法:由於 \(2\) 不是合數,所以 \(2\) 是質數
如此厲害的結論竟然是 \(nekko\) 在賴床的時候發現的,當時的 \(nekko\) 躺在床上玩雙人成行,在玩的過程中就發現了這一偉大的定理,從此,數學界的一個新世界,由 \(czy\) 構建的一個嶄新的數學世界誕生了
「大於二質數構造公理」
內容:如果一個整數大於 \(2\),並且不能被任何一個小於這個數的質數整除,那么這個數是質數。
證明:太高深了,不太會證,希望有神犇來幫忙證明一下
「質數自反性公理」
如果一個整數是質數,那么它是質數。
證明:需要用到邏輯學中的大量文獻,這里暫時略過了
挫折與發展
在CZY提出「三大公理」后,有人提出「非陳數學」,即否定了公理一而保留公理二三。令人遺憾的是,此人在研究了一段時間后,發現「非陳數學」中並不存在任何質數。但是,此人卻在「非陳數學」中證明了「黎曼猜想」不成立。所以說,"禍兮福所倚,福兮禍所伏"。
他人的評價
"數學的基石,文化的先行,宇宙的奠定"——ZTB瞻望「三大公理」
最新的進展
CZY指出,由絕對值的知識,有 \(|-1| = 1\);由行列式的知識,有 \(|-1| = -1\)。因此得出結論 \(2=0\).
位運算
一般均是在二進制下來說
基本運算
1.與 (&) 兩個對應位都為 \(1\) 時才為 \(1\)
2.或 (|) 只要兩個對應位中有一個 \(1\) 時就為 \(1\)
3.異或(^) 只有兩個對應位不同時才為 \(1\)
取反(~)
把0變成1,把1變成0
補碼:在二進制表示下,正數和 的補碼為其本身,負數的補碼是將其對應正數按位取反后加一。
應用:\(\text{lowbit}(x)=(x)\&(-x)\) 取出最后一個等於1的位數,具體原因想一想
左移和右移
num<<i
表示將 \(num\) 的二進制表示向左移動1位所得的值。
num>>i
表示將 \(num\) 的二進制表示向右移動1位所得的值。
注意:+
和-
的優先級高於左移右移
高精度
#include<iostream>
#include<string.h>
#include<stdio.h>
#include<cstdlib>
#include<algorithm>
#define base 100000
#define cut 5
#define L 10000
using namespace std;
struct Big{
int len;
long long num[L];
Big(){
len=1;
memset(num,0,sizeof(num));
}
Big operator=(const Big &a){
len=a.len;
for(int i=0;i<len;i++) num[i]=a.num[i];
return *this;
}
long long &operator[](int a){
return num[a];
}
long long operator[](int a)const{
return num[a];
}
friend istream&operator>>(istream&,Big&);
friend ostream&operator<<(ostream&,Big&);
};
int operator>(const Big a,const Big b){
if(a.len!=b.len){
if(a.len>b.len) return 1;
return 0;
}
for(int i=a.len-1;i>=0;i--)
if(a[i]>b[i]) return 1;
return 0;
}
int operator==(const Big a,const Big b){
if(a.len!=b.len) return 0;
for(int i=0;i<a.len;i++)
if(a[i]!=b[i])
return 0;
return 1;
}
int operator<(const Big a,const Big b){
if(a>b || a==b)return 0;
return 1;
}
Big operator+(Big a,Big b){
Big ret;
long long carry=0;
for(int i=0;;i++){
ret[i]=a[i]+b[i]+carry;
carry=ret[i]/base;
ret[i]%=base;
if(i>=a.len&&i>=b.len&&carry==0)
break;
}
ret.len=min(L,max(a.len,b.len)+10);
while(ret.len>0&&ret[ret.len-1]==0)
ret.len--;
if(ret.len==0)
ret.len=1;
return ret;
}
Big operator+(Big a,int b){
long long carry=b;
for(int i=0;;i++){
a[i]+=carry;
carry=a[i]/base;
a[i]%=base;
if(a[i]==0 && carry==0 && i>=a.len)
break;
}
a.len=min(L,a.len+10);
while(a.len>0 && a[a.len-1]==0) a.len--;
return a;
}
Big operator*(Big a,Big b){
Big ret;
for(int i=0;i<a.len;i++)
for(int j=0;j<b.len;j++) ret[i+j]+=a[i]*b[j];
long long carry=0;
for(int i=0;;i++){
ret[i]+=carry;
carry=ret[i]/base;
ret[i]%=base;
if(ret[i]==0 && carry==0 && i>=a.len+b.len-1) break;
}
a.len=min(L,a.len+b.len+10);
while(a.len>0 && a[a.len-1]==0) a.len--;
return a;
}
Big operator*(Big a,int b){
long long carry=0;
for(int i=0;;i++){
carry+=a[i]*b;
a[i]=carry%base;
carry/=base;
if(carry==0&&a[i]==0&&i>=a.len) break;
}
a.len=min(L,a.len+10);
while(a.len>0&&a[a.len-1]==0) a.len--;
return a;
}
Big operator-(Big a,Big b){
long long carry=0;
for(int i=0;;i++){
a[i]-=b[i]+carry;
if(a[i]<0) carry=(-a[i]/base+1);
else carry=0;
a[i]+=carry*base;
if(carry==0&&i>=b.len) break;
}
while(a.len>0&&a[a.len-1]==0) a.len--;
return a;
}
Big operator-(Big a,int b){
long long carry=b;
for(int i=0;;i++){
a[i]-=carry;
if(a[i]<0) carry=(-a[i]/base+1);
else carry=0;
a[i]+=carry*base;
if(carry==0) break;
}
while(a.len>0 && a[a.len-1]==0) a.len--;
return a;
}
Big operator/(Big a,int b){
long long carry=0;
for(int i=a.len-1;i>=0;i--){
a[i]+=carry*base;
carry=a[i]%b;
a[i]/=b;
}
while(a.len>0&&a[a.len-1]==0) a.len--;
return a;
}
Big operator%(const Big a,int b){
return a-(a/b)*b;
}
int main(){
return 0;
}
數論
整除性質
同余性質
最大公約數與最小公倍數
若干個數所有約數中的共同的最大數是最大公約數,倍數中共同的最小數是最小公倍數
一般兩個數的最大公約數是 \(\gcd\) 最小公倍數是 \(\mathrm{lcm}\)
\(\gcd(a,b)*\mathrm{lcm}(a,b)=a*b\)
求法:輾轉相除
int gcd(int a,int b){return a%b==0?a:gcd(b,a%b);}
整除分塊
整除分塊是用於快速處理形似
的式子的辦法
引理1
證明略
引理2
對於一個較大的 \(n\) 我們顯然會發現,我們后面的這個下取整取值並不是每一次都隨着 \(i\) 而變化的,它是呈塊狀分布的,同時這個下取整的值我們也能得到,應該是共有 \(2\sqrt n\) 個值
結論:
通過嚴謹的數學推理(打表) 我們發現實際上這些取值是有規律的,即
如果一個塊的開始位置時 \(l\) 那么它的結束位置 \(r\) 就是 \(\lfloor \dfrac n {\lfloor \frac n l \rfloor} \rfloor\)
那么我們寫程序的基本結構就很顯然了
for(int l=1,r;l<=n;l=r+1) r=n/(n/l);
所以我們對於形如:
可以對 \(f(i)\) 可以直接前綴和預處理,就可以解決這道問題了
各種篩法
埃氏篩
用於統計從1-n中的質數
原理
:從前往后篩,如果篩到一個數\(i\),然后把\(i\)的倍數都刪了
時間復雜度:\(O(n\log n \log n)\) 基本可以跑到2e7
void statistics(int n){
for(int i=2;i<=n;i++){
if(!st[i]) prime[++k]=i;
for(int j=1;prime[j]*i<=n;j++){
st[prime[j]*i]=true;
}
}
}
線性篩
這個東西很重要,關系到后面的積性函數求解
先放一個代碼:
void primes(int n){
for(int i=2;i<=n;i++){
if(!st[i]) prime[++k]=i;
for(int j=1;prime[j]*i<=n;j++){
st[i*prime[j]]=true;
if(!(i%prime[j])) break;
}
}
}
我們觀察和上面那個的區別
發現無非就是多了一行if(i%prime[j]==0) break;
我們考慮對於這一行來說,它的意義本質上是篩到 \(i\) 的最小質因子時停止
我們給個證明:
首先我們假設我們的 \(i\) 篩到 \(prime_j\) 的時候結束
1. 對於 \(prime_j\) 我們有 \(prime_j\) 是 \(i\) 的最小質因子,原因是若不是的話,這個循環會提前的break掉,不會讓我們跑到 \(j\) 這個位置
2. 對於 \(i\times prime_k(1\le k < j)\) 的最小質因子恆為 \(prime_k\),原因是 \(i\) 的最小質因子是 \(prime_j\) 對於 \(prime_k\) 恆小於 \(prime_j\)
3. 對於 \(i\times prime_k(k > j)\) 的最小質因子應該是 \(prime_j\),理由同上,這句話說明了,對於 \(i\times prime_k\) 來說,它在這個 \(prime_j\) 時已經被篩過一次了,所以不必要繼續往后篩
由於每個點只會被最小質因子篩一次,這樣線性篩的復雜度就可以被優化到 \(O(n)\) 了
歐拉篩
我們可以把這件求phi分成兩步
k是質數
首先我們容易發現一個問題:如果\(k\)是質數,那么\(\varphi (k)\)顯然等於\(k-1\),因為小於\(k\)的數都與\(k\)互質
所以我們可以把這一句:if(!vis[i]) p[++num]=i;
再加上一句phi[i]=i-1;
-
\(i\bmod p=0\) 時
若 \(p\mid i\) 且 \(p\) 為質數 則 \(\varphi(i*p)\)=\(p*\varphi(i)\)
證明:
若 \(p \mid i\) 則可以推出 \(p\) 是 \(i\) 的一個質因子
我們發現:一個數的歐拉函數與每一項的次數無關
所以說
同時
發現兩個式子除了 \(i*p\) 全部相同
所以我們可以推出 \(\varphi(i*p)\)=\(p*\varphi(i)\)
證畢
那么很顯然在這一句中if(i%prime[j]==0) break;
我們可以加上phi[prime[j]*i]=prime[j]*phi[i]
-
\(i\%p\not =0\) 時
\(p\) 一定是 \(i*p_j\) 的最小質因子
先把 \(\varphi(i)\) 的式子擺在這
\[\varphi(i)=i*(1-\frac{1}{p_1})*(1-\frac{1}{p_2})...*(1-\frac{1}{p_k}) \]然后
\[\varphi(p*i)=p*i*(1-\frac{1}{p_1})*(1-\frac{1}{p_2})...*(1-\frac{1}{p_k}*(1-\frac{1}{p})) \]我們把下面的式子除以上面的式子發現:
\[\varphi(p*i)/\varphi(i)=p*(1-\frac{1}{p})=p-1 \]把 \(\varphi(i)\) 挪到右邊最后可以得到
\[\varphi(p*i)=\varphi(i)*(p-1) \]所以我們討論完了所有的情況,可以得到最終式子:
inline void get_euler(int n) {
phi[1] = 1;
for (int i = 2; i <= n; ++i) {
if (!st[i]) prime[++cnt] = i, phi[i] = i - 1;
for (int j = 1; j <= cnt && prime[i] * j <= n; ++j) {
st[prime[j] * i] = true;
if (i % prime[j] == 0) {
phi[i * prime[j]] = phi[i] * (prime[j] -1);
break;
}
phi[i * prime[j]] = phi[i] * prime[j];
}
}
}
莫比烏斯篩
這個比較簡單,就不寫了
inline void get_euler(int n){
phi[1]=1;
for(int i=2;i<=n;i++){
if(!st[i]){
mu[i]=-1
phi[i]=i-1;
prime[++cnt]=i;
}
for(int j=1;prime[j]*i<=n && j<=cnt;j++){
st[prime[j]*i]=true;
if(i%prime[j]==0){
phi[i*prime[j]]=prime[j]*phi[i];
break;
}
mu[i*prime[j]]=-mu[i];
phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
}
杜教篩
杜教篩用來處理形如 \(S(n)=\sum_{i=1}^nf(i)\),且 \(f(i)\) 是積性函數的式子
首先我們設 \(S(i)\) 表示 \(\sum_{x=1}^if(x)\)
因此
分解質因數
試除法
用於單個數檢驗是否為質數
原理
:從1枚舉到\(\sqrt n\)觀察是否有數能被n整除
void prime(int x){
int n=x;
for(int i=2;i*i<=n;i++){
if(x%i==0)
while(n%i==0)
vec[x].push_back(i),n/=i;
}
if(n) vec[x].push_back(n);
}
Miller-Rabin
用法:\(O(k\log^2n)\) 判斷 \(n\) 是否是質數。其中 \(k\) 是枚舉底數的個數,\(2^{78}\) 以內只需要枚舉 \(12\) 個。
費馬小定理:
若 \(p\) 是質數且 \(p \nmid a\)
我們可以根據費馬小定理
得出一種檢驗素數的思路(Fermat 素性測試
):
它的基本思想是不斷地選取在 \([2,n)\) 中的 \(a\),並檢驗是否每次都有 \(a^{n−1}\equiv 1 \pmod n\)
但是很遺憾,費馬小定理的逆定理並不成立
卡邁克爾數:如果有一個合數 \(n\) 滿足任何一個 \(a\) 都滿足 \(a^{n−1}\equiv 1 \pmod n\),那么我們稱這個合數 \(n\) 為卡邁克爾數,同時,滿足 \(m=2^n-1\) 那么 \(m\) 還是卡邁克爾數,所以卡邁克爾數是無窮的
二次探測定理:
若 \(p\) 是奇素數,則 \(x^2 \equiv 1 \pmod p\) 的解為 \(x=1\) 或 \(x=p-1\) \(\pmod p\)
算法流程:
(1)對於偶數和 0,1,2 可以直接判斷。
(2)設要測試的數為 \(x\),我們取一個較小的質數 \(a\),設 \(s,t\),滿足\(2^s\times t=x-1\) (其中 \(t\) 是奇數)。
(3)我們先算出 \(a^t\),然后不斷地平方並且進行二次探測(進行 \(s\) 次)。
(4)最后我們根據費馬小定律,如果最后 \(a^{x-1}\not\equiv 1 \pmod x\),則說明 \(x\) 為合數。
(5)多次取不同的 \(a\) 進行 \(Miller-Rabin\) 素數測試,這樣可以使正確性更高
備注:
(1)我們可以多選擇幾個 \(a\),如果全部通過,那么 \(x\) 大概率是質數。
(2)\(Miller-Rabin\) 素數測試中,“大概率”意味着概率非常大,基本上可以放心使用。
(3)當 \(a\) 取前12個素數時,可以證明 \(2^{78}\) 范圍內的數不會出錯。
(5)另外,如果是求一個 \(\text{long long}\) 類型的平方,可能會爆掉,因此有時我們要用快速乘,不能直接乘。
\(\text{Pollard-Rho}\) 先咕了
裴蜀定理
對於二元一次方程 \(ax+by=c\),有整數解的充要條件是 \(\gcd(a,b) \mid c\)。
推論:當 \(ax+by=1\) 時,當且僅當 \(\gcd(a,b)=1\) 時有解。
證明:
設 \(d=\gcd(a,b)\) 則 \(d \mid a,d\mid b\)。由整除的性質 \(\forall x,y\in Z\),有 \(d \mid (ax+by)。\)
設 \(s\) 為 \(ax+by\) 最小正值,令 \(q=\left\lfloor \dfrac{a}{s} \right\rfloor\)。
則 \(r=a\pmod s=a-q(ax+by)=a(1-qx)+b(-qy)\)。
可見 \(r\) 也為的線性組合。
由於 \(r\) 為 \(a\pmod s\) 所得,所以 \(0\leq r<s\)。
由於 \(s\) 為線性組合的最小正值,可知。
-
因此有 $ s\mid a$ ,同理 \(s\mid b\) ,因此,\(s\) 是 \(a\) 與 \(b\) 的公約數,所以 \(d\ge s\)。
因為 \(d\mid a\) ,\(d\mid b\) ,且 \(s\) 是 \(a\) 與 \(b\) 的一個線性組合,所以由整除性質知 \(d\mid s\)。
-
但由於 \(d\mid s\) 和 \(s>0\),因此 \(d\le s\)。
由 1,2 得 \(d=s\) ,命題得證。
exgcd
現在我們對於 \(ax+by=c\) 想要獲得一組整數解。
首先 \(c\) 肯定是要滿足 \(\gcd(a,b) \mid c\) 的(要不然裴蜀定理白證明了)。
然后我們可以直接求 \(a_1x+b_1y=\gcd(a_1,b_1)\) 的一組整數解,最后 \(x,y\) 再乘上 \(c/\gcd(a_1,b_1)\) 即可
好了,我們現在說怎么求解。
當 \(b=0\) 時,顯然 \(x=1,y=0\)
當 \(b\not= 0\) 時,有:
\(ax+by=\gcd(a,b)\)
\(\because\gcd(b,a\bmod b)=\gcd(a,b)\)
\(\therefore a x + b y = \gcd ( a , b ) = \gcd ( b , a \bmod b ) = b \times t x + ( a \bmod b ) \times t y\)
\(\because a \bmod b=a- \left\lfloor \dfrac{a}{b} \right\rfloor\times b\)
\(\therefore a x+b y=b\times tx+(a- \left\lfloor \dfrac{a}{b} \right\rfloor\times b)\times ty=a\times ty+b\times(tx−\left\lfloor \dfrac{a}{b} \right\rfloor\times ty)\)
\(\therefore x=ty,y=tx-\left\lfloor \dfrac{a}{b} \right\rfloor\times ty\)
此時我們容易發現: \(x\) 的一組解為 \(ty\),\(y\) 的一組解是 \(tx-\lfloor \dfrac a b \rfloor ty\),都被轉化成了兩個更小的數,這兩個解可以從 \(b \times t x + ( a \bmod b ) \times t y\) 中求解,當邊界達到 \(b = 0\) 時,這道題就可以解決了
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#define ll long long
using namespace std;
ll exgcd(ll a, ll b, ll& x, ll& y) {
if (!b) {
x = 1;
y = 0;
return a;
} else {
ll tx, ty;
ll d = exgcd(b, a % b, tx, ty);
x = ty;
y = tx - (a / b) * ty;
return d;
}
}
ll a, b, k, x, y;
int main() {
scanf("%lld%lld%lld", &a, &b, &k);
ll d = exgcd(a, b, x, y);
if (k % d) {
puts("no solution!");
return 0;
}
else {
x = x * k / d;
y = (k - a * x) / b;
}
printf("%lld %lld\n", x, y);
return 0;
}
歐拉定理
證明:
設 \(x_1,x_2,\dots,x_{\varphi(m)}\) 是 \([1,m]\) 里面與 \(m\) 互質的數,由於在 \(\bmod m\) 意義下兩兩不同且余數都與 \(m\) 互質
因此我們推理:\(ax_i\) 必定也是 \(\bmod m\) 意義下兩兩不同且余數都與 \(m\) 互質的數
所以:
拓展歐拉定理
推理:[Oi Wiki](歐拉定理 & 費馬小定理 - OI Wiki)
乘法逆元
定義:若 \(ax\equiv 1\pmod b\) 且 \(a\) 與 \(b\) 互質,那么我們就能定義 \(x\) 為 \(a\) 的逆元,記為 \(a^{-1}\) ,所以我們也能稱 \(x\) 為 \(a\) 在 \(\pmod b\) 意義下的倒數,此時我們對於 \(\dfrac{a}{b}~\pmod p\),我們就可以求出 \(b\) 在 \(\pmod p\) 意義下的逆元,來代替 \(\dfrac{1}{b}\)
快速冪
由費馬小定理:若 \(p\) 為素數,\(a\) 為正整數,且 \(a\) 、\(p\) 互質。則有 \(a^{p-1}\equiv 1\pmod p\)
所以
所以我們用快速冪算出來 \(a\times b^{p-2}\) 就有 \(\dfrac{a}{b}\) 的逆元的值了
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
typedef long long ll;
int mod;
ll power(ll k, ll p) {
ll ans = 1;
while (p) {
if (p & 1)
ans = ans * k % mod;
k = k * k % mod;
p >>= 1;
}
return ans;
}
ll a, b;
int main() {
scanf("%lld%lld", &a, &mod); //算a的逆元
printf("%lld", a * power(b, mod - 2) % mod);
return 0;
}
拓展歐幾里得
求解 \(ax\equiv c \pmod b\) 中 \(c=1\) 的情況。我們可以轉化為求解 \(ax+by=1\) 的解
所以:
ll exgcd(ll a, ll b, ll& x, ll& y) {
if (!b) {
x = 1;
y = 0;
return a;
} else {
ll tx, ty;
ll d = exgcd(b, a % b, tx, ty);
x = ty;
y = tx - (a / b) * ty;
return d;
}
}
int a, p, x, y;
int main() {
scanf("%d%d", &a, &p);
int d = exgcd(a, p, x, y);
x = (x % p + p) % p;
}
線性求逆元
設 \(p=kx+r\)
容易得到:\(k = \lfloor \dfrac p x\rfloor\),\(r = p\bmod x\)
那么我們很容易發現:\(kx+r\equiv 0\pmod p\)
此時,我們左右同時乘上 \(x^{-1}r^{-1}\) 可得
\(kr^{-1}+x^{-1}\equiv 0\pmod p\)
\(\therefore x^{-1}\equiv -kr^{-1}\pmod p\)
把 \(k=\lfloor \dfrac{p}{x} \rfloor\),\(r=p \bmod x\) 代入,得
\(x^{-1}\equiv -\left\lfloor \dfrac{p}{x} \right\rfloor \times (p \bmod x)^{-1} \pmod p\)
階乘求逆元
首先,有如下的一個關系:
\(inv_{i+1}=\dfrac{1}{(i+1)!}\)
\(inv_{i+1}*(i+1)=\dfrac{1}{i!}\times \dfrac{1}{i+1}\times (i+1)=\dfrac{1}{i!}=inv_{i!}\)
所以我們可以先求出 \(n\) 的逆元,然后逆推即可
同時,我們也可以發現 \(\dfrac{1}{i}\) 的逆元就是:\(\dfrac{1}{i!}\times (i-1)!\)
Lucas
定理:
inline int lucas(int a, int b, int p) {
if (a < p && b < p)
return C(a, b, p);
return C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}
中國剩余定理(CRT)
定理:對於下列一些式子的整數求出符合條件的最小的正整數 \(x\)
首先,若\((m_1,m_2\dots m_n)\)兩兩互質
我們即可直接令 \(M=m_1\times m_2\dots\times m_n\)
則令 \(M_i=\dfrac{M}{m_i}\)
當 \(x\) 滿足 \(x = x_1\times M_1\times M_1^{-1}+x_2\times M_2\times M_2^{-1}\dots\) 時
一定符合上面的式子
莫比烏斯反演
形式一:
證明:
我們容易發現:對於右邊的式子 \(\sum_{i\mid x}g(i) \sum_{d\mid \frac x i} \mu(d)\)
結合莫比烏斯函數剛才推的性質2
,我們很容易得到該式 \(=f(x)\)
得證
或者我們直接用卷積的方法證明:
原式即證:
我們讓左邊的那個式子的左右兩邊同時卷上 \(\mu\)
得:
得證
形式二:
多項式
快速傅里葉變換(FFT)
復數
設 \(a,b\) 為實數,\(i^2=-1\),形如 \(a+bi\) 的數叫復數,其中 \(i\) 被稱為虛數單位,復數域是目前已知最大的域
在復平面中,\(x\) 代表實數,\(y\) 軸(除原點外的點)代表虛數,從原點 \((0,0)\) 到 \((a,b)\) 的向量表示復數 \(a+bi\)
模長:從原點 \((0,0)\) 到點 \((a,b)\) 的距離,即 \(\sqrt{a^2+b^2}\)
幅角:假設以逆時針為正方向,從 \(x\) 軸正半軸到已知向量的轉角的有向角叫做幅角
計算:平行四邊形法則(其實就是分配律),注意這里的 \(i^2\) 為 \(-1\):
幾何定義:復數相乘,模長相乘,極角相加
這里放一張圖促進理解:

模長就是 \(\sqrt{a^2+b^2}\),極角就是 \(\theta\)
代數計算方法:\((a+bi)\times (c+di)=ac+bdi^2+bci+adi=ac-bd+(bc+ad)i\)
多項式表示法
系數表示法:
設 \(A(x)\) 表示一個\(x-1\) 次多項式
則 \(A(x)=\sum_{i=0}^{n} a_i * x^i\)
例如:\(A(3)=2+3\times x+x^2\)
利用這種方法計算多項式乘法復雜度為 \(O(n^2)\)
(第一個多項式中每個系數都需要與第二個多項式的每個系數相乘)
利用這種方法計算多項式乘法的時間復雜度為 \(O(n^2)\)
點值表示法
將 \(n\) 互不相同的 \(x\) 帶入多項式,會得到 \(n\) 個不同的取值 \(y\)
則該多項式被這 \(n\) 個點 \((x_1,y_1),(x_2,y_2)\dots(x_n,y_n)\) 唯一確定
其中 \(y_i=\sum_{j=0}^{n-1} a_j\times x_i^j\)
例如:上面的例子用點值表示法可以為 \((0,2)~(1,5)~(2,12)\)
利用這種方法計算多項式乘法的時間復雜度仍然為 \(O(n^2)\)
可以發現,大整數乘法復雜度的瓶頸可能在“多項式轉換成點值表示”這一步(以及其反向操作),只要完成這一步就可以 \(O(n)\) 求答案了。
單位根
定義
在后面我們默認 \(n\) 為 2 的整數次冪
在復平面上,以原點為圓心,1為半徑作圓,所得的圓叫單位圓。以圓點為起點,圓的 \(n\) 等分點為終點,做 \(n\) 個向量,設幅角為正且最小的向量對應的復數為 \(\omega_n\),稱為 \(n\) 次單位根。
根據復數乘法的運算法則,其余 \(n-1\) 個復數為 \(\omega_n^2,\omega_n^3\ldots\omega_n^n\)
計算它們的值,我們可以用歐拉公式:\(\omega_{n}^{k}=\cos\ k *\dfrac{2\pi}{n}+i\sin k*\dfrac{2\pi}{n}\)
單位根的幅角為周角的 \(\dfrac{1}{n}\)
在代數中,若 \(z^n=1\),我們把 \(z\) 稱為 \(n\) 次單位根
單位根的性質與反演
單位根的性質:
- \(\omega _n ^k =\cos~k\dfrac{2\pi}{n}+i\times \sin~k\dfrac{2\pi}{n}\)
- \(\omega _{2n}^{2k}=\omega _n^k\)
- \(\omega_n^{k+\frac{n}{2}}=-\omega _n^k\)
- \(\omega_n^0=\omega _n^n=1\)
- \((\omega _n^k)^2=\omega_n^{2k}\)
第二條的證明(如果看成一個復數,就可以發現實際上這個數沒有變化):
\(\omega ^{2k}_{2n}=\cos ~2k\dfrac{2\pi}{2n}+i\sin2k\dfrac{2\pi}{2n}\)
約分后就和原來一樣了
第三條的證明(如果把它看成一個復數,就可以理解成實數域和虛數域都取反了):
單位根反演:
證明:
當\(~k\mid n\) 時: 由 \(\omega_n^0=\omega_n^n~~~\) 得:\(\omega_n^{ik}=1\) 故原式等於1
當 \(k\nmid n\) 時: 原式乘上 \(\omega_n^k\) 可化成
②減①得:
易得上式為0
complex
C++的STL提供了復數模板!
頭文件:#include <complex>
定義: complex<double> x;
運算:直接使用加減乘除
為什么要使用單位根作為\(x\)代入
規定我們帶入的點值為 \(n\) 個單位根
設 \((y_0,y_1,y_2\dots y_{n-1})\) 為多項式 \(A(x)\) 的離散傅里葉變換(即把 \(\omega_n^0,\omega_n^1\dots\omega_n^{n-1}\) 代入上式后的結果)
我們再設一個多項式 \(B(x)\) 其各位系數為上述的 \(y\)
現在,我們把上述單位根的倒數,即 \(\omega_n^{-1},\omega_n^{-2}\dots\omega_n^{-n}\) 代入 \(B(x)\) 得 \((z_0,z_1\dots z_{n-1})\),那么有
最下面括號里的式子 \(\sum_{j=0}^{n-1}(\omega_n^{j-k})^i\) 顯然是能求的
當 \(k=j\) 時,該式為 \(n\)
當 \(k\ne j\) 時
通過等比數列求和可以得出
所以我們有
因此我們可以發現:
把多項式\(A(x)\)的離散傅里葉變換結果作為另一個多項式\(B(x)\)的系數,取單位根的倒數即 \(\omega ^0_n,\omega ^{−1}_n,\omega ^{−2}_n,...,\omega ^{-(n−1)}_n\)作為 \(x\) 代入 \(B(x)\),得到的每個數再除以 \(n\),得到的就是 \(A(x)\) 的各項系數
離散傅里葉變換(DFT)的數學證明
我們考慮把一個普通的多項式按奇偶性分類,有:
所以我們設成兩個多項式:
\(A_1(x)=a_0+a_2x+a_4x^2+⋯+a_{n−2}x^{\frac{n}{2}-1}\)
\(A_2(x)=a_1+a_3x+a_5x^2+⋯+a_{n−1}x^{\frac{n}{2}-1}\)
因此\(A(x)=A_1(x^2)+xA_2(x^2)\)
假設當前\(k<\frac{n}{2}\),現再我們要代入 \(x=\omega_n^k\)
我們再代入 \(x=\omega_n^{k+\frac{n}{2}}\)
因此,只要我們求出 \(A_1(x)\) 和 \(A_2(x)\) 分別在 \(\omega_{\frac{n}{2}}^0,\omega_{\frac{n}{2}}^1,\omega_{\frac{n}{2}}^2\dots\omega_{\frac n 2} ^{\frac n 2}\) 等的點值表示,就可以 \(O(n)\) 的求出 \(A(\omega_n^{1\sim\frac n 2})\) 的點值表示,同時可以得到 \(A(\omega_n^{\frac n2 + 1\sim n})\),正好 \(n\) 個,每個 \(A_1\) 和 \(A_2\) 也這么求,持續分治,分治的邊界是 \(n=1\)
另外一邊我們是離散傅里葉逆變換(IDFT) 也就這么理解就行了
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const int MAXN = 2e6 + 10;
inline int read() {
char c = getchar(); int x = 0, f = 1;
while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') {x = x * 10 + c - '0'; c = getchar();}
return x * f;
}
const double Pi = acos(-1.0);
struct complex {
double x, y;
complex (double xx = 0, double yy = 0){x = xx, y = yy;}
}a[MAXN], b[MAXN];
complex operator + (complex a,complex b){ return complex(a.x + b.x, a.y + b.y);}
complex operator - (complex a,complex b){ return complex(a.x - b.x, a.y - b.y);}
complex operator * (complex a,complex b){ return complex(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);}
void fast_fast_tle(int len, complex *a, int type){
if (len == 1) return ;
complex a1[len >> 1], a2[len >> 1];
for (int i = 0; i <= len; i += 2) a1[i >> 1] = a[i], a2[i >> 1] = a[i | 1];
fast_fast_tle(len >> 1, a1, type);
fast_fast_tle(len >> 1, a2, type);
complex Wn = complex(cos(2.0 * Pi / len), type * sin(2.0 * Pi / len)), w = complex(1,0);
for (int i = 0; i < (len >> 1); i++, w = w * Wn)
a[i] = a1[i] + w * a2[i],
a[i + (len >> 1)] = a1[i] - w * a2[i];
}
int main(){
int N = read(), M = read();
for (int i = 0; i <= N; i++) a[i].x = read();
for (int i = 0; i <= M; i++) b[i].x = read();
int len = 1;
while (len <= N + M) len <<= 1;
fast_fast_tle(len, a, 1);
fast_fast_tle(len, b, 1);
//type為1表示從系數變為點值
//-1表示從點值變為系數
for (int i = 0; i <= len; i++) a[i] = a[i] * b[i];
fast_fast_tle(len, a, -1);
for (int i = 0;i <= N + M; i++) printf("%d ",(int)(a[i].x / len + 0.5));//按照我們推導的公式,這里還要除以n
return 0;
}
優化 FFT
遞歸優化
在進行 \(\text{fft}\) 時,我們要把各個系數不斷分組並放到兩側,那么一個系數原來的位置和最終的位置有什么規律呢?
初始位置:0 1 2 3 4 5 6 7
第一輪后:0 2 4 6|1 3 5 7
第二輪后:0 4|2 6|1 5|3 7
第三輪后:0|4|2|6|1|5|3|7
用|
隔開各組數據
我們把二進制拉出來,發現一個位置a上的數,最后所在的位置是a二進制翻轉得到的數
那么我們可以據此寫出非遞歸版本 \(\mathrm{FFT}\):先把每個數放到最后的位置上,然后不斷向上還原,我們每次把相鄰的兩塊共同貢獻給上面的那一塊,同時求出點值表示。
蝴蝶操作
貌似也沒啥,就是把東西先存上再用,感覺直接看模板就懂了,差不多得了,還是直接背吧
/*
BlackPink is the Revolution
light up the sky
Blackpink in your area
*/
const int mod = 1e9 + 7;
const int N = 3e6 + 5;
const double Pi = acos(-1.0);
int n, m, T, L, lim, rev[N];
struct cp {
double x, y;
cp() {x = 0, y = 0;}
cp(double a, double b) {x = a, y = b;}
}f[N], g[N], ans[N];
cp operator + (cp a, cp b) {return cp(a.x + b.x, a.y + b.y);}
cp operator - (cp a, cp b) {return cp(a.x - b.x, a.y - b.y);}
cp operator * (cp a, cp b) {return cp(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);}
inline void FFT(cp *a, int type) {
rep (i, 0, lim - 1) if (i < rev[i]) swap(a[i], a[rev[i]]);
for (int len = 1; len < lim; len <<= 1) {
cp wn(cos(Pi / len), sin(Pi / len) * type);
for (int i = 0; i < lim; i += (len << 1)) {
cp w(1, 0), x, y;
rep (j, 0, len - 1) {
x = a[i + j], y = w * a[i + j + len];
a[i + j] = x + y, a[i + j + len] = x - y;
w = w * wn;
}
}
}
}
int main(){
read(n, m);
for (lim = 1; lim <= n + m; lim <<= 1) L++;
rep (i, 0, n) read(f[i].x);
rep (i, 0, m) read(g[i].x);
rep (i, 0, lim - 1) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (L - 1));
FFT(f, 1);
FFT(g, 1);
rep (i, 0, lim) ans[i] = f[i] * g[i];
FFT(ans, -1);
rep (i, 0, n + m) write((int)(ans[i].x / lim + 0.5), ' ');
return 0;
}
//write:RevolutionBP
NTT
階
設 \(r,n\) 是互素的整數, \(r \not = 0\) ,\(n>0\) ,使得 \(r^x\equiv 1 \pmod n\) 成立的最小正整數
\(x\) 稱為 \(r\) 模 \(n\) 的階,標為 \(\delta _n r\)
原根
如果 \(r,n\) 都是互素的正整數,當 \(\text{ord}_nr=\varphi(n)\) 時,稱 \(r\) 是模 \(n\) 的原根,即 \(r\) 是 \(n\) 的原根
我們令 \(n\) 為大於 \(1\) 的 \(2\) 的冪,\(p\) 為素數且 \(n \mid (p-1)\),\(g\) 為 \(p\) 的一個原根
我們設
所以
我們發現,原根包含着單位根的所有性質,所以我們就可以用原根代替單位根
最大優點:保持精度不變
特殊記憶:\(998244353\) 的原根是 \(3\) 和 \(114514\)
/*
BlackPink is the Revolution
light up the sky
Blackpink in your area
*/
const int mod = 998244353;
const int N = 5e6 + 5;
const int G = 114514;
const double Pi = acos(-1.0);
#define int long long
int n, m, T, b, lim, rev[N], f[N], g[N], ans[N];
const int invG = power(G, mod - 2);
inline void FFT(int *a, int type) {
rep (i, 0, lim - 1) if (i < rev[i]) swap(a[i], a[rev[i]]);
for (int len = 1; len < lim; len <<= 1) {
int wn = power(type == 1 ? G : invG, (mod - 1) / (len << 1));
for (int i = 0; i < lim; i += (len << 1)) {
int w = 1, x, y;
rep (j, 0, len - 1) {
x = a[i + j], y = 1ll * w * a[i + j + len] % mod;
a[i + j] = (x + y) % mod, a[i + j + len] = (x - y + mod) % mod;
w = 1ll * w * wn % mod;
}
}
}
if (type == 1) return ;
int inv = power(lim, mod - 2);
rep (i, 0, lim) a[i] = a[i] * inv % mod;
}
signed main(){
read(n, m);
for (lim = 1; lim <= n + m; lim <<= 1);
rep (i, 0, n) read(f[i]);
rep (i, 0, m) read(g[i]);
rep (i, 0, lim - 1) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) * (lim >> 1));
FFT(f, 1), FFT(g, 1);
rep (i, 0, lim) ans[i] = f[i] * g[i];
FFT(ans, -1);
rep (i, 0, n + m) write(ans[i], ' ');
return 0;
}
//write:RevolutionBP
多項式乘法逆
題目描述
給定一個多項式 \(F(x)\) ,請求出一個多項式 \(G(x)\), 滿足 \(F(x)*G(x)\equiv 1\pmod{x^n}\)。系數對 \(998244353\) 取模。
首先,若 \(x\) 只有 \(1\) 項時,我們的 \(G(x)\) 就是 \(x\) 的逆元,不然的話,我們就可以遞歸求解
我們假設當前已知:
由於
顯然:
兩式相減,我們就可以得到:
左右同時把 \(F(x)\) 去掉,則有
然后我們左右同時平方可以發現:
拆開后有:
再給兩邊乘上 \(F(x)\),化簡
我們發現已經用 \(H(x)\) 和 \(F(x)\) 來代替 \(G(x)\) 了。
然后我們遞歸去找 \(H(x)\) 就行
inline void Inv(ll *X, ll *Y, ll len) {
if (len == 1) return Y[0] = inv(X[0]), void();
Inv(X, Y, (len + 1) >> 1);
for (lim = 1; lim < (len << 1); lim <<= 1);
static ll H[N];
rep (i, 0, len - 1) H[i] = X[i];
rep (i, len, lim - 1) H[i] = 0;
NTT(H, 1, lim), NTT(Y, 1, lim);
rep (i, 0, lim)
Y[i] = ((2ll - Y[i] * H[i] % mod) + mod) % mod * Y[i] % mod;
NTT(Y, -1, lim);
rep (i, len, lim - 1) Y[i] = 0;
}
多項式對數函數(多項式 ln)
題目描述
給出 \(n-1\) 次多項式 \(F(x)\) ,求一個 \(\bmod x^n\) 下的多項式 \(G(x)\),滿足 \(G(x)\equiv \ln F(x)\pmod {x^n}\)
首先,我們令 \(f(x)=\ln(x)\)
則原式可化為 \(G(x)\equiv f(F(x))\pmod{x^n}\)
我們對兩邊同時求導有:
\(G'(x) \equiv f'(F(x))F'(x) \pmod {x^{n-1}}\)
由於 \(\ln'(x)=\dfrac{1}{x}\)
\(G'(x)\equiv\dfrac{F'(x)}{F(x)} \pmod{x^{n-1}}\)
那么我們此時再積回去,得到最終式子
至於積分和求導的求解,直接看就行
求導公式
積分公式
inline void Direv(int *A, int *B, int len) { //求導
for (int i = 1; i < len; ++i) B[i - 1] = mul(A[i], i); B[len - 1] = 0;
}
inline void Inter(int *A,int *B,int len){ //積分
for(int i = 1; i < len; ++i) B[i] = mul(A[i - 1], ksm(i, P - 2)); B[0] = 0;
}
void Ln(int *a, int *b, int len) {
Direv(a, A, len), Inv(a, B, len);int l = len << 1;
NTT(A, 1, l), NTT(B, 1, l);
for (int i = 0; i < l; ++i) A[i] = mul(A[i], B[i]);
NTT(A, -1, l), Inter(A, b, len);
}
多項式開根
題目描述
給定一個 \(n-1\) 次多項式 \(A(x)\) ,在 \(\bmod x^n\) 意義下的多項式 \(B(x)\) ,使得 \(B^2(x)\equiv A(x) \pmod {x^n}\)。若有多解,請取零次項系數較小的作為答案。
這個和上面那個差不多吧,頂多就是換個推導過程
假設我們已知:
易知:
我們繼續推導可得:
由於題目要求0次項系數最小的最為答案,所以
直接進行多項式求逆和NTT即可
t{A_0}=1$
泰勒展開
看了知乎上一個回答,倒是蠻有趣的

首先對於這個函數,我們定義它為 \(f(x)\)
我們現在想做的就是構造一個函數 \(g(x)\) 和它億分相似
首先我們就要找一個點和它重合,哪個點呢?顯然 \((0,1)\) 是最好的選擇
然后我們就會選擇在 \((0,1)\) 處進行 \(n\) 階求導,然后找到一個函數 \(g(x)\) 使得這 \(p(p\in [1,n])\) 階的導數全部和 \(f(x)\) 的 \(p\) 階導數相同,然后我們會考慮,可能一次兩次的,我們願意算,但是太多了肯定就不願意了,所以這個 \(g(x)\) 得長得靠譜,還得好算
所以我們有了一個絕妙的主意,我們用多項式來代替,眾嗦粥之,眾所周知,我們的 \(n\) 次多項式 \(n\) 階求導以后可是個常數啊,這一點就會非常好用
這里的圖引自知乎,第一條回答(號主已經把號注銷了,所以我也沒法確認,如有侵權希望可以和我私聊)
滿足二階導數時,我們的圖長這樣
滿足四階導數時,我們的圖長這樣
很容易想到,如果我們的函數無窮次求導以后,這兩個函數就會無限接近
比如說我們要求 \(\cos 2\),這很難求,但是我們肯定知道 \(\cos \dfrac \pi 2\) 的值,那么直接進行一個很多次的求導,然后在我的構造函數上就能找到近似值
那么我們考慮能不能直接用代數式子直接推出來呢?
答案是肯定的,剛才我們說,如果我們 \(n\) 次求導以后兩個函數會無限相似
首先容易得到,如果我們是 \(n\) 次求導,那么最后我們得到的式子長這樣
首先容易得到 \(a_0=g(0)=f(0)\)
當 \(g^n(x)\) 時,原式長成 \(n!a_n\)。
容易得到: \(g^n(0)=f^n(0)=n!a_n\)
所以
綜上:
若我們不是從 \((0,f(0))\) 開始的,而是從 \((x_0,f(x_0))\) 開始的,那么這個式子改一改就有了
在 \(\mathrm{OI}\) 中,我們很多東西都是有精度限制的,請問我們這個式子什么時候才能算到我們要的誤差之內呢?
首先我們發現,我們的式子是越來越小的,原因我們可以這樣理解
泰勒展開是先把函數的大框架構建完畢,然后精細化的過程,所以你后面的是基於前面的框架搭建的所以是越來越小,越來越精細的過程
我們會發現,其實我們的式子可遠不止這些,我們后面的式子可以無限拉長,類似這樣:
假設現在我們把 \(g\) 精確到 \(n\) 階了, 那么后面的那一堆就是誤差項,有一個叫佩亞諾的倒霉蛋試着去化簡這個誤差項,結果后來啥也沒搞出來,不過他用后面的和前面做了個商,算出來了一個東西,不夠為了紀念他,我們還是將這個誤差項叫作佩亞諾余項
他算了個這玩意:
然后就是兩位天才的故事拉格朗日和柯西
首先是一個簡單的問題,給你一段時間內汽車走過的路程與時間的關系(\(ST\) 圖像),然后我告訴你平均速度是 \(v_1\),然后還告訴你其中某一點的速度是 \(v_0<v_1\),那么我們很容易知道一定會出現一個點的速度 \(v_2\) 使得 \(v_2>v_1\)
然后這個叫拉格朗日的神仙就直接把這個寫成了一個方程,我們稱其為拉格朗日中值定理:
然后柯西拓展了一下,變成了柯西中值定理
然后我們回歸原來我們的誤差項等一系列定義
設
我們設 \(T(x)=(x-x_0)^{n+1}\),且 \(T(x_0)=R(x_0)=0\)
我們讓等式兩邊同時除以 \(T(x)\),並且使用柯西中值定理,有
然后我們很容易發現,我們仿照剛才的方式容易得到,上面的那個玩意可以無窮次求導,並且長得和我們剛才的 \(R(x)\) 除了每一項前面加了個系數 \((n+y)\) 以外都一樣,下面的話也是,並且下面除去 \((n+1)\) 以外也一樣,分開寫出來
分子:
所以
於是我們可以設 \(P(\xi)=R^{'}(\xi)\) 且 \(P(x_0)=0\)
分母:
我們可以直接設 \(Q(\xi)=T^{'}(\xi)\),且 \(Q(x_0)=0\)
發現原式變成了 \(\dfrac {P(\xi)} {Q(\xi)}\),很巧合我們可以繼續用柯西中值定理歸納
很容易發現我們可以一直這樣歸納最終的結果就會是
這樣的話就非常非常非常好了,我們不用讓 \(x\) 趨近於 \(x_0\),直接就能算出一個點的誤差值
老實說,后面的這一堆東西我們一般也就數學分析的時候用,OI中還是泰勒展開直接應用比較多,原因是生成函數等對於 \(x^n\) 是無效的,所以我們不用考慮這么多,直接套公式使用即可
牛頓迭代
牛頓迭代就是說,我們給定一個連續的函數 \(f(x)\),然后我們隨便找它上面的一個點,然后作切線,發現這個切線會和 \(x\) 軸有一個交點,然后我們以這個交點作垂線,交函數上一個點,然后 繼續上述操作,我們最終會發現我們的這個和 \(x\) 軸的交點會無限逼近 \(f(x)\) 和 \(x\) 軸的交點
代數方法說明的話就是我們設剛開始函數上這個交點是 \((x_n,f(x_n))\),那么和 \(x\) 軸交在了 \((x_{n+1},0)\)。我們重復這個過程,如下圖

而且我們順帶說一下切線方程:我們如果已知 \(f(x)=kx+b\),那么我們的 \(k\) 可以用導數表示出來,即 \(f(x)=f^{'}(x)+b\) 我們稱其為切線方程,對於剛才這個玩意我們也可以用類似這種方式解決,我們要求 \(x_{n+1}\) 的值,即求 $f(x_n)+f^{'}(x_n)\times(x_{n+1}-x_n)=0 $,我們搞一搞:
同理我們把橫坐標及函數轉化成多項式形式,就有 :
多項式指數函數(多項式 exp)
題目描述
給出 \(n-1\) 次多項式 \(A(x)\),求一個 \(\bmod x^n\) 下的多項式 \(B(x)\),滿足 \(B(x)\equiv e^{A(x)}\)。系數對 \(998244353\) 取模
首先對於兩邊同時取 \(\ln\),沒什么好說的
我們令 \(F(G(x))=\ln B(x)- A(x)\equiv 0\pmod {x^n}\)
並且我們容易得到:
代入牛頓迭代以后得到:
化簡后得:
到這里,最簡單的多項式已經都有了,剩下的也是這些的拓展和加深,所以我們可以放出vector封裝好的多項式模板了
/*
Blackpink is the Revolution
light up the sky
Blackpink in your area
*/
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cctype>
#include <bitset>
#include <vector>
#include <cstdio>
#include <cmath>
#include <queue>
#include <ctime>
#include <map>
#include <set>
using namespace std;
using ll = long long;
using P = pair<int ,int>;
using poly = vector <ll>;
namespace scan {
template <typename T>
inline void read(T &x) {
x = 0; char c = getchar(); int f = 0;
for (; !isdigit(c); c = getchar()) f |= (c == '-');
for (; isdigit(c); c=getchar()) x = x * 10 + (c ^ 48);
if (f) x = -x;
}
template <typename T, typename ...Args>
inline void read(T &x, Args &...args) {
read(x), read(args...);
}
template <typename T>
inline void write(T x, char ch) {
if (x < 0) putchar('-'), x = -x;
static short st[30], tp;
do st[++tp] = x % 10, x /= 10; while(x);
while (tp) putchar(st[tp--] | 48);
putchar(ch);
}
template <typename T>
inline void write(T x) {
if (x < 0) putchar('-'), x = -x;
static short st[30], tp;
do st[++tp] = x % 10, x /= 10; while(x);
while(tp) putchar(st[tp--] | 48);
}
inline void write(char ch){
putchar(ch);
}
template <typename T, typename ...Args>
inline void write(T x, char ch, Args ...args) {
write(x, ch), write(args...);
}
} //namespace scan
using namespace scan;
#define rep(i, a, b) for(ll i = (a); (i) <= (b); ++i)
#define per(i, a, b) for(ll i = (a); (i) >= (b); --i)
const ll mod = 998244353;
const ll inv2 = 499122177;
const int N = 2e6 + 5;
ll n, m, T;
namespace Poly {
#define Size(_) int(_.size())
#define lg(x) ((x) == 0 ? -1 : __lg(x))
const ll Primitive_Root = 114514;
const ll invPrimitive_Root = 137043501;
poly rev;
inline ll qpow(ll x, ll k) {
ll res = 1;
while (k) {
if (k & 1) res = res * x % mod;
x = x * x % mod;
k >>= 1;
}
return res;
}
inline ll inv(ll x) {return qpow(x, mod - 2);}
inline void NTT(ll *a, ll lim, ll type) {
rev.resize(lim);
rep (i, 0, lim - 1) {
rev[i] = rev[i] = (rev[i >> 1] >> 1) | ((i & 1) * (lim >> 1));
if (i < rev[i]) swap(a[i], a[rev[i]]);
}
for (ll len = 1; len < lim; len <<= 1) {
ll wn = qpow(type == 1 ? Primitive_Root : invPrimitive_Root, (mod - 1) / (len << 1));
for (ll i = 0; i < lim; i += (len << 1)) {
ll w = 1, x, y;
rep (j, 0, len - 1) {
x = a[i + j] % mod, y = w * a[i + j + len] % mod;
a[i + j] = (x + y) % mod, a[i + j + len] = (x - y + mod) % mod;
w = w * wn % mod;
}
}
}
if (type == 1) return ;
ll inv_len = inv(lim);
rep (i, 0, lim - 1) a[i] = a[i] * inv_len % mod;
}
poly operator * (poly F, poly G) {
ll siz = Size(F) + Size(G) - 1, lim = (1 << (lg(siz - 1) + 1));
if (siz <= 300) {
poly H(siz);
per (i, Size(F) - 1, 0)
per (j, Size(G) - 1, 0)
H[i + j] = (H[i + j] + F[i] * G[j] % mod) % mod;
return H;
}
F.resize(lim), G.resize(lim);
NTT(F.data(), lim, 1);
NTT(G.data(), lim, 1);
rep (i, 0, lim - 1) F[i] = F[i] * G[i] % mod;
NTT(F.data(), lim, -1);
F.resize(siz);
return F;
}
poly operator + (poly F, poly G) {
int siz = max(Size(F), Size(G));
F.resize(siz);
G.resize(siz);
rep (i, 0, siz - 1) F[i] = F[i] + G[i] % mod;
return F;
}
poly operator - (poly F, poly G) {
int siz = max(Size(F), Size(G));
F.resize(siz);
G.resize(siz);
rep (i, 0, siz - 1) F[i] = (F[i] - G[i] + mod) % mod;
return F;
}
poly cut(poly F, ll len) {
F.resize(len);
return F;
}
poly Direv(poly F) {
int siz = Size(F) - 1;
rep (i, 0, siz - 1) F[i] = F[i + 1] * (i + 1) % mod;
F.pop_back();
return F;
}
poly inter(poly F) {
F.emplace_back(0);
per (i, Size(F) - 1, 0) F[i] = F[i - 1] * inv(i) % mod;
F[0] = 0;
return F;
}
poly Inv(poly F) {
int siz = Size(F), lim = (1 << (lg(siz - 1) + 1));
poly G;
G.resize(1);
G[0] = inv(F[0]);
F.resize(lim);
for (int len = 2; len <= lim; len <<= 1) {
poly tmp(len << 1, 0);
rep (i, 0, len - 1) tmp[i] = F[i];
G.resize(len << 1);
NTT(tmp.data(), len << 1, 1), NTT(G.data(), len << 1, 1);
rep (i, 0, (len << 1) - 1)
G[i] = G[i] * (mod + 2ll - tmp[i] * G[i] % mod) % mod;
NTT(G.data(), (len << 1), -1);
G.resize(len);
}
G.resize(siz);
return G;
}
poly ln(poly F) {
int siz = Size(F);
return cut(inter(cut(Direv(F) * Inv(F), siz)), siz);
}
poly exp(poly F) {
int siz = Size(F);
poly G{1};
for (int i = 2; (i >> 1) < siz; i <<= 1) {
G = G * (poly{1} - ln(cut(G, i)) + cut(F, i));
G.resize(i);
}
G.resize(siz);
return G;
}
poly sqrt(poly F) {
int siz = Size(F);
poly G{1};
for (int i = 2; (i >> 1) < siz; i <<= 1) {
G = (G + (cut(F, i) * cut((Inv(cut(G, i))), i))) * poly{inv2};
G.resize(i);
}
G.resize(siz);
return G;
}
}// namespace Poly
using namespace Poly;
namespace RevolutionBP {
ll lim, L;
void main() {
read(n);
poly F(n);
for (auto& i : F) read(i);
F = Poly::sqrt(F);
for (auto& i : F) write(i, ' ');
return void();
}
}
signed main(){
RevolutionBP::main();
return 0;
}
//write: RevolutionBP
生成函數
生成函數(generating function),又稱母函數,是一種形式冪級數,其每一項的系數可以提供關於這個序列的信息。
生成函數有許多不同的種類,但大多可以表示為單一的形式:
其中 \(k_n(x)\) 被稱為核函數,不同的核函數會導出不同的生成函數,分成3類
普通生成函數:\(k_n(x)=x^n\) (OGF)
指數生成函數:\(k_n(x)=\dfrac {x^n} {n!}\) (EGF)
狄利克雷生成函數:\(k_n(x)=\dfrac {1} {n^x}\) (DGF)
另外,對於生成函數 \(F(x)\),我們用 \([k_n(x)]F(x)\) 來表示它的第 \(n\) 項的核函數對應的系數,也就是 \(a_n\)
容易發現其中的 \(x\) 對於原式中的結果並沒有什么影響,我們想要的應該是固定項前面的系數
普通生成函數
形式:
\(a\) 既可以是有窮序列,也可以是無窮序列
若干例子:
序列 \(a=\langle 1,2,3\rangle\) 的OGF:\(1+2x+3x^2\)
序列 \(a=\langle 1,1,1,\dots,1\rangle\) 的OGF:\(\displaystyle\sum_{n} x^n\)
序列 \(a=\langle 1,2,4,8,16,\dots\rangle\) 的OGF:\(\displaystyle \sum_n 2^nx^n\)
序列 \(a=\langle 1,3,5,7,9,\dots\rangle\) 的OGF:\(\displaystyle \sum_n (2n+1)x^n\)
基本運算
考慮兩個序列 \(a,b\) 的普通生成函數,分別為 \(F(x),G(x)\),那么有
因此 \(F(x)\pm G(x)\) 是序列 \(\\langle a_n\pm b_n \rangle\)
考慮乘法運算,也就是卷積:
封閉形式
這是OGF比較好玩的一個東西,就是說每次寫成一個多項式真的很難受,但是如果能夠把這個形式冪級數寫成其它的形式可能就會好很多
對於上述例二,容易發現:
很容易想到移項后可以得到:\(F(x)=\dfrac {1}{1-x}\)
可能會有點疑惑,這個東西怎么還能是負數?
實際上發現,如果要讓前面的形式冪級數在 \(\infty\) 處收斂,x的范圍應在 \(\in (-1,1)\)
但是還是不需要去理它,因為我們生成函數的本質不在於這個 \(x\) 的取值,或者說對於這個封閉形式,只是一種方便推過去和推回來的過程罷了,並不是對原式的具體闡述
有若干種物品 ,每種物品只有1件,求取 \(n\) 件物品的總方案數。
每種物品的生成函數是 \(1\times x^0+1\times x^1\)
那么若干個物品乘起來就是 \((x+1)^n\)
然后用二項式定理展開一下就可以得到:
\(\displaystyle (x+1)^n=\sum_{i=0}^n \binom {n} {i}x^i\)
如果學過組合數學就會發現這個確實很對
當然也有比較惡心的
有若干種物品 ,每種物品可以取任意件,求取 \(n\) 件物品的總方案數
和上個題一樣,每個物品的生成函數是:\(\displaystyle\sum_{i=0}^n x^i\) 很快就能發現,又等於 \(\dfrac 1 {1-x}\)
這樣的話 \(m\) 件物品的生成函數就是
同時可以用隔板法理解,答案是相同的
來個重頭戲:斐波那契數列:
前置知識:
\(\displaystyle\dfrac {1} {1-kx}=\sum_{i=0}k^ix^i\)
設 \(F(x)\) 表示斐波那契數列的生成函數
首先第一步拆分,容易得到:
容易發現下面兩個相加就只和第一個差出來一個數1
\(A-xA-x^2A=1\)
整理一下:\(A=\dfrac {1}{1-x-x^2}\)
然后發現這和上面補充的前置知識太相似了,如果能做到和前面的那個完全一樣就好了
然后就是很多套路性的轉化了
第一個因式分解,\(1-x-x^2=(1-ix)(1-jx)\)
解得 \(i=\dfrac {1+\sqrt 5} {2}~~j =\dfrac {1-\sqrt 5} {2}\)
但是這時候還是兩個 \(\dfrac {1} {1-kx}\) 的卷積,容易發現既然根式都出來了,上面的1也順路拆了算了
令 \(ai+bj=1\),得到 \(a=\dfrac {1} {{\sqrt 5}}i~~b=-\dfrac {1} {\sqrt 5}j\)
帶回原來的式子里,裂項以后拆成一個巨無霸式子:
\(A=\dfrac {i} {\sqrt 5}\dfrac {1}{1-ix}-\dfrac{j}{\sqrt 5}\dfrac{1}{1-jx}\)
寫的更加簡便一點:
\(\displaystyle A = \dfrac {M} {1-ax}+\dfrac {N} {1-bx}=\sum_{k=0}(Ma^k + Nb^k)x^k\)
同時 \(M=\dfrac {\sqrt 5 i} {5},N=-\dfrac {\sqrt 5 j} {5},a=i,b=j\)
然后就是前面的基本多項式運算了
\(\displaystyle F(x)=\sum_{k=0}((\dfrac {\sqrt 5 i} {5}) \times (\dfrac {1+\sqrt 5} 2)^k-\dfrac {\sqrt 5 j} {5}\times (\dfrac {1-\sqrt 5} {2})^k)x^k\)
經過一點點化簡就能得到:
到這里其實要說的也就差不多了
P2000 拯救世界
為了拯救世界,小 a 和 uim 決定召喚出 kkksc03 大神和 lzn 大神。根據古籍記載,召喚出任何一位大神,都需要使用金木水火土五種五行神石來擺一個特定的大陣。而在古籍中,記載是這樣的:
金神石的塊數必須是 \(6\) 的倍數
木神石最多用 \(9\) 塊
水神石最多用 \(5\) 塊
火神石的塊數必須是 \(4\) 的倍數
土神石最多用 \(7\) 塊
金神石的塊數必須是 \(2\) 的倍數
木神石最多用 \(1\) 塊
水神石的塊數必須是 \(8\) 的倍數
火神石的塊數必須是 \(10\) 的倍數
土神石最多用 \(3\) 塊
有多少種方案
直接列出所有的生成函數:
\(1+x^6+x^{12}+⋯=\dfrac 1 {1 − x^{6}}\)
\(1+x^2+x^{3}+⋯+x^9=\dfrac {1 − x^{10}} {1-x}\)
\(1+x^2+x^{3}+⋯+x^5=\dfrac {1 − x^{6}} {1-x}\)
\(1+x^4+x^{8}+⋯=\dfrac {1 − x^{8}} {1-x}\)
\(1+x^2+x^{4}+⋯=\dfrac 1 {1 − x^{2}}\)
\(1+x=\dfrac {1-x^2} {1 − x}\)
\(1+x^8+x^{16}+⋯=\dfrac 1 {1 − x^{8}}\)
\(1+x^{10}+x^{20}+⋯=\dfrac 1 {1 − x^{10}}\)
\(1+x+x^2+x^3=\dfrac {1-x^4} {1 − x}\)
直接全部撐起來,發現很多都能化簡,最后得到了 \(\dfrac {1} {(1-x)^5}\)
根據上面得到的
直接發現第 \(n\) 項就是 \(\binom {n+4} {4}=\)\(\dfrac{(n+4)(n-3)(n-2)(n-1)} {24}\)
組合數學
加法 & 乘法原理
加法原理:
做一件事情,完成它有 \(n\) 類方式,第一類方式有 \(M_1\) 種方法,第二類方式有 \(M_2\) 種方法,\(\dots\) 第 \(n\) 類方式有 \(M_n\) 種方法,那么完成這件事情共有 \(M_1+M_2+……+M_n\) 種方法。
乘法原理:
做一件事,完成它需要分成 \(n\) 個步驟,做第一 步有 \(m_1\) 種不同的方法,做第二步有 \(m_2\) 種不同的方法,……,做第 $n $ 步有 \(mn\) 種不同的方法。那么完成這件事共有 \(N=m_1\times m_2\times m_3\times \dots\times mn\) 種不同的方法。
排列與組合基礎
排列數
\(n\) 個數中任取 \(m\) 個數,考慮順序,用 \(A_m^n\) 或者 \(P_m^n\) 表示即可,計算公式如下:
由於是排列數,可以理解成 \(n\) 個人選 \(m\) 個人出來排隊,問共有多少種排隊順序
組合數
從 \(n\) 個不同元素中,任取 \(m\) 個元素組成一個集合,問有多少種集合,用 \(C_n^m\) 或者 \(\binom {n} {m}\) 表示,不過一般人應該都喜歡后面的那個,計算公式如下 :
特別規定當 \(m > n\) 時,原式為 \(0\)
二項式定理
數學歸納法很容易得到
也可以直接把 \((a+b)^n\) 直接拆開成乘積的形式,那么我們考慮 \(a^rb^{n-r}\) 的系數,就是我們在這 \(n\) 個 \(a+b\) 中,每次選一個 \(a\) 或者 \(b\),選 \(r\) 個 \(a\) 的方案數,因此就是 \(\binom n r\)
拓展為多項式形式(廣義二項式定理)
組合數求和定理
三種理解方式:
第一種,我們把式子左邊理解成從 \(n\) 個球里面取 \(0\) 個球,\(1\) 個球,\(\dots\),\(n\) 個球的方案,其實加起來以后就是從 \(n\) 個球中任意取的總方案數,我們把右邊理解成每個球取或不取都有一種方案總共就是 \(2^n\) 種方案,很容易發現它們是等價的
第二種,用數學歸納法,很簡單,不想寫了
第三種,用二項式定理證明:令 \(a=b=1\) 即可直接得出
排列組合數性質和推論
具體數學中最重要的二項式系數一共給了十個,這里直接列出,還夾帶了一個私貨
多重集的排列數 多重集的組合數
多重集的定義:可以包含相同元素的集合
設 \(S=\{n_1\times a_1,n_2\times a_2,\dots,n_ka_k\}\),表示由 \(n_i\) 個 \(a_i\) 組成的多重集,多重集的排列數為
多重集的排列數和多重組合數是一個東西,多重集的排列數是另一個東西
多重集的組合數:我們從 \(S\) 里面選取 \(r\) (\(1\le r<n_i,\forall i\in [1,k]\))個元素組成多重集的方案數為
不相鄰的排列
\(1\sim n\) 中選 \(k\) 個,這 \(k\) 個數中任何兩個數不相鄰的組合有 \(\displaystyle \binom {n - k + 1} k\)
我們可以理解成我們先從原來的里面摳出來 \(k-1\) 個格,使得剩下的就可以隨便選了,選完以后我們把這空着的幾個格移動到選取的相鄰兩個之間即可
錯位排列
我們考慮有 \(n\) 個數,有多少種方案使得他們沒有一個在正確的位置上
正解是分兩種情況討論,對於第 \(i\) 個信封來首如果前面的 \(n-1\) 個都裝錯了,那么第 \(n\) 個信封和前面的任意一個交換即可,如果前面的 \(n-1\) 個中有一個裝對了,別的都裝錯了,那么我們就讓第 \(n\) 個信封和那個對的交換即可
只有這兩種方式可以使得一次交換以后為錯排,所以我們的總方案數為
圓排列
\(n\) 個人圍成一圈的排列數 \(Q_n^n\)
卡特蘭數
卡特蘭數用於處理這樣的組合問題:我們默認一個行為是加一,另一個行為是減一,那么我們要保證前綴和恆非負即可
這樣的話我們的卡特蘭數可以有一個組合意義:對於一個 \(n\times n\) 的網格圖,我們找到 \(y=x\) 這條線,要求從 \((0,0)\) 開始走到 \((n,n)\) 且不能超出 \(y=x\) 的路徑條數
我們欽定向左走是 \(1\),向右走是 \(-1\)
首先我們不考慮 \(y=x\) 的限制,那么 \(ans = \displaystyle \binom {2n} n\)
我們考慮當不合法時,路徑一定會經過 \(y=x+1\) 這條線,那么我們把這些路徑經過 \(y=x+1\) 以后的那段沿着 \(y=x+1\) 對稱上去以后,就變成了從 \((0,0)\) 到 \((n-1,n+1)\) 的一段,這顯然是 \(\displaystyle \binom {2n} {n+1}\)
兩者相減即可
因此我們的最終遞推式為 \(Catalan_n=\displaystyle \binom {2n} n - \displaystyle \binom {2n} {n+1}=\dfrac 1 {n+1} \displaystyle \binom {2n} n\)
rancy 引理
對於 \(x_1,x_2\dots x_m\),若 \(\displaystyle \sum _{i=1}^mx_i=1\),則必定存在一種圓排或者說循環位移使得恰好有一個滿足前綴和都是正數

我們首先繪成前綴和折線統計圖,大概是長成這個模樣的
然后我們找到最低點,如果有多個就去找最右側的

我們很容易發現,對於這個來說,從點 \(low\) 到最后一個點的前綴和 \(\mid 1-val_{low}\mid\)
中間的幾段可以抵消掉,最后還會剩下最后一段上升的和最開始的一段上升的,合起來就是 \(\mid 1-val_{low}\mid\)
那么從 \(0\) 號點開始的那個點最壞情況下就是直接往下,此時最多減少 \(val_{low}\) 顯然前面的比 \(val_{low}\) 要大,原因是 \(val_{low}\) 的值恆 \(\le 0\),后面的一堆就是起起伏伏但是一段的和都為0,且由於剛才我們的那一段已經到了最低點,所以后面的都是先上升一段后下降一段,必然不會造成影響,因此我們就證明了必然存在一個點使得前綴和不大於 \(0\)
那么怎么證明唯一性呢?
首先如果我們的 \(low\) 點以后的和為 \(\mid 1-val_{low}\mid\),這個值是 \(\ge 1\) 的,所以 \(low\) 點之前的點的和必然 \(\le 0\),這些點必然不合法
同時我們的 \(low\) 點后面的那個點必然是上升,所以說這條線段的增加量 \(\ge 1\),因此如果你選擇 \(low\) 后面的點的話,你在最后才能加上這一段,那你在到達 \(low\) 點的時候,你的線段和至多為 \(-val_{low}\),加上 \(val_{low}\) 后必然 \(\le0\)
因此我們通過反證法證明了存在且唯一存在
容斥原理
首先我們考慮比較簡單的一些操作
如果班里面語文好的有 15 人,數學好的有 25 人,英語好的有 20人,那么請問至少喜歡一門的有多少人?
答案很顯然不能是直接相加,因為會有重復的
此時我們引入容斥原理
化簡后得到
但是證明很容易,我們考慮單獨拎出來一個元素,假設它出現在集合 \(T_1,T2\dots T_n\) 計算其出現的次數,那么它們這些集合正好就是一些個單個貢獻,兩兩貢獻等等,最后就是我們下面的這些東西了
顯然后面的這個是二項式定理,直接求解就有 \(1-0=1\)
對於全集 U 下的 集合的並 可以使用容斥原理計算,而集合的交則用全集減去 補集的並集 求得:
容斥定理一般化
容斥原理常用於集合的計數問題,而對於兩個集合的函數 \(f(S),g(S)\),若
則
證明太難了,可以看這,自己也寫一下,不過不需要掌握
后半部分求和與Q無關,先不考慮
記關於集合 \(P\) 的函數 \(F(P)=\sum_{T\subseteq P}(-1)^{|P|-|T|}\),並化簡這個函數:
因此原來的式子的值是
發現僅當 \(\displaystyle |S\setminus Q|=0\) 時有 \(0^0=1\),此時 \(Q=S\),對答案的貢獻恰好為 \(g(S)\),其他時候都為0,於是有
推論:
則
理論來說這個推論是補集形式,和上面差不多
斯特林數
第二類斯特林數:可以記作:\(S(n,k)\),或 \(\begin{Bmatrix} n\\k\end{Bmatrix}\)(吐槽一句太難寫了吧)
表示將 \(n\) 個兩兩不同的元素,划分為 \(k\) 個互不區分的非空子集的方案數。
遞推式:
邊界是 \(\begin{Bmatrix} n\\k\end{Bmatrix}=[n=0]\)
組合意義的證明就是:
單獨放進一個新的子集,有 \(\begin{Bmatrix} n-1\\k-1\end{Bmatrix}\) 種方案
放入一個已經有的子集里面有 \(k\begin{Bmatrix} n-1\\k\end{Bmatrix}\) 種方案,兩者加起來就行
通項公式:
使用容斥原理證明該公式。設將 \(n\) 個兩兩不同的元素,划分到 \(k\) 個兩兩不同的集合(允許空集)的方案數為 \(G_k\),將 \(n\) 個兩兩不同的元素,划分到 \(k\) 個兩兩不同的非空集合(不允許空集)的方案數為 \(F_k\)。
於是我們有:
\(\displaystyle G_k=k^n\)
\(\displaystyle G_k=\sum_{j=0}^k\binom {k} {j} F_j\)
第一個式子的話,每一個數都有 \(k\) 個位置可以選,所以即為 \(k^n\)
第二個式子的話,我們可以理解成先空出來 \(j\) 個位置,此時的答案是 \(\displaystyle \binom k jF(j)\),我們把空出來的整到一塊就是這個式子了
二項式反演式子如下:
證明網上看看得了,不會想證
因此我們可以用二項式定理去化簡上面的那個東西(警告:開始大力推式子)
考慮 \(F_i\) 和 \(\begin{Bmatrix} {n}\\{i}\end{Bmatrix}\) 的關系,相當於這一堆集合,我們可以任意變換位置,那么就和組合數和排列數的區別一樣了,因此 \(F_i\) 應該是
\(\displaystyle \begin{Bmatrix} {n}\\{i}\end{Bmatrix}\) 的 \(i!\) 倍,因此
二項式反演
形式1:
證明:
然后右邊乘上一個 \(1^{n-j-t}\) 就是一個二項式定,只有 \(j=n\) 的時候右側的求和為 1,其余的時候都是 0
兩邊恆成立,得證
形式2:
證明:
更改一個我疑惑了很長時間的誤區:
建議先看后面再回來看這個
后面我們將會說至多和至少的概念,這里和我們傳統理解的是不一樣的,這里的至多至少的方案數內部是有被重復計算的,這里之所以會重復是因為至少和至多的方案數是由欽定產生的。
至多是欽定 \(i\) 個不受限制選。
至少是欽定 \(i\) 個受限制選。
舉個例子,假如說一個人有 \(n\) 樣物品,問你從中至少選 \(k\) 樣的方案數,假設 \(f(i)\) 這里表示至少選 \(i\) 種物品的方案數,那么 \(f(i)\) 就應該是先欽定選了 \(i\) 個,然后剩下的都是可選可不選,這么算出來的,列出式子應該是 \(\displaystyle \binom n i 2^{n-i}\)。實際上很容易發現,這樣算是有重復計算的部分,假設 \(g(i)\) 表示恰好選 \(i\) 種的方案數,我們想用 \(g(i)\) 來表示 \(f(i)\),就不能只是單純的 \(\displaystyle \sum_{j=i}^n g(j)\) 后綴和了,容易發現,\(f(i)\) 應該是由 \(\displaystyle\sum_{j=i}^n \binom j i g(j)\) 得到,這里的系數可以想作是在這 \(j\) 個元素里面欽定的是哪幾個,后面的至多是同理的,不過是欽定和非欽定的表現
二項式反演有幾個很重要的套路
恰好和至多的轉換
設 \(f_i\) 表示恰好時的方案數,\(g_i\) 表示至多時的方案數,易得
由形式1可以反演
這樣就可以從至多的方案數推出恰好的方案數
恰好和至少的轉換
設 \(f_i\) 表示恰好 \(i\) 個滿足某種條件時的方案數,\(g_i\) 表示欽定 \(i\) 個滿足某種條件時的方案數,易得
由形式2可以反演