7.6 數學 學習筆記
7.6 gyx math
第一部分 康託展開
變進位制數
給定無窮數集\(S=\{a_0,a_1,a_2,...,a_n,...\},a_0=1\)
定義變進位制數:第\(i\)單位是上一位單位的\(a_i\)倍
要求變進位制數\((A)_s=\overline{x_nx_{n-1}...x_1x_0},0\leq x_i <a_{i+1}\)
\(k\)進位制數:\(\forall i>0,a_i=k\)
結合我們常見的\(k\)進位制數來理解,變進位制數其實就是每一位上的進位制不一樣
對於變進位制數\(A\)有
\[\begin{aligned}A&=\sum_{i=0}^n(x_i\prod_{j=0}^ia_j)\\ &=x_n\prod_{i=0}^n a_i+x_{n-1}\prod_{i=0}^{n-1}a_i+...+x_1a_1a_0+x_0a_0 \end{aligned} \]廣義康託展開
變進位制數下的進位制轉換
對於兩個變進位制數\(S_1,S_2\),給出\((A)_{S_1}\),求\((A)_{S_2}\)
一種可操作的思路是\((A)_{S_1}\rightarrow (A)_{10}\rightarrow (A)_{S_2}\)
其中第一步操作可以直接利用變進位制數的定義\(O(n)\)求出
第二步操作採用短除法,每次除以當前位的進位制,餘數即為該位結果
int s1[N],s2[N],a[N],res[N]; void Cantor(int len){ ll A=0; //(A)s1→(A)10 for(int i=len-1;i>=0;i--) A=(A+a[i])*s1[i]; //(A)10→(A)s2 for(int i=0;A;i++) res[i]=A%s2[i+1],A/=s2[i+1]; }
康託展開
康託展開:將任意十進位制自然數\((A)_10\)變換為階乘進位制數\((A)_!\)
階乘進位制數:要求\(a_i=i\)
若\((A)_!=\overline{x_nx_{n-1}...x_1x_0}\),有
\[X=a_n(n-1)!+a_{n-1}(n-2)!+...+a_1\cdot0! \]要求\(\forall i\in [0,n],0\leq x_i< i+1\),此時康託展開唯一
逆康託展開
將任意階乘進位制數\((A)_!\)變換為十進位制自然數\((A)_{10}\)
與廣義康託展開一致,這裡的\(\prod_{i=0}^na_i=i!\)
參考上面廣義康託展開程式碼
排列與變進位制數
引例:火星人
任何一個\(1,...,n\)的排列\(p_1,p_2,...,p_n\)都對應唯一的階乘進位制數
構造雙射變換\(f:P→S_!:x_i=rank\_suf(p_i)\)
即\(p_i\)在後綴\({p_i,p_{i+1},...,p_n}\)中的排名,從0開始
對於排列變階乘進位制數,採用的資料結構要求有維護數集,插入刪除,查詢排名的功能
使用樹狀陣列和線段樹都是 \(O(nlogn)\) 的
對於階乘進位制數變排列,採用的資料結構要求有維護數集,查詢排名為\(k\)的數並刪除的功能
- 線段樹二分 複雜度\(O(nlogn)\)
- 樹狀陣列 複雜度\(O(nlog^2n)\)
推薦寫線段樹二分
int t[4*N];
void pushup(int x){
t[x]=t[x<<1]+t[x<<1|1];
}
void add(int x,int l,int r,int p,int dlt){
if(l==r){t[x]+=dlt;return;}
int mid=(l+r)>>1;
if(p<=mid) add(x<<1,l,mid,p,dlt);
else add(x<<1|1,mid+1,r,p,dlt);
pushup(x);
}
int sum(int x,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr) return t[x];
int res=0,mid=(l+r)>>1;
if(ql<=mid) res+=sum(x<<1,l,mid,ql,qr);
if(qr>mid) res+=sum(x<<1|1,mid+1,r,ql,qr);
return res;
}
int query(int x,int l,int r,int cnt){
if(l==r) return l;
int mid=(l+r)>>1;
if(cnt<=t[x<<1]) return query(x<<1,l,mid,cnt);
return query(x<<1|1,mid+1,r,cnt-t[x<<1]);
}
ll n,a[N],x[N];
int main(){
n=read();
//排列變階乘進位制數
for(int i=n-1;i>=0;i--) a[i]=read();
for(int i=0;i<n;i++){
x[i]=sum(1,1,n,1,a[i]);
add(1,1,n,a[i],1);
}
for(int i=n-1;i>=0;i--) printf("%d ",x[i]);
printf("\n");
//階乘進位制數變排列
for(int i=n-1;i>=0;i--) x[i]=read();
memset(t,0,sizeof t);
for(int i=1;i<=n;i++) add(1,1,n,i,1);
for(int i=n-1;i>=0;i--){
a[i]=query(1,1,n,x[i]+1);
add(1,1,n,a[i],-1);
}
for(int i=n-1;i>=0;i--) printf("%d ",a[i]);
return 0;
}
例題
闆闆
給定\(\;1,...,n(n\leq 10^5)\;\)的排列\(\;p_1,p_2,...,p_n\;\)和一個正整數\(\;\alpha\;\):
操作\(\;1\;\):將當前排列排名增加\(\;k\;(k\leq 10_{18})\)
操作\(\;2\;\):輸出當前排列對應的\(\sum_{i=1}^np_i\times \alpha ^i \;mod\;998244353\)
操作次數不大於排列長度
Tips:$20!\approx2\times 10^{18},25! \approx 1.5\times 10^{25} $
Bonus:操作\(\;1\;\)改為繼承歷史版本
我們觀察\(k\)的範圍可以知道,增加一次排名最多影響的是最後20位的數字,所以完成整道題影響到的數字範圍最多是後25位,所以前面的數字的答案我們可以提前處理好
剩下25位轉成階乘進位制數暴力修改就可以了
事板子題
至於繼承歷史版本……
逆序對
跟上面題一樣,只是操作2改成了求當前排列的逆序數,用樹狀陣列\(O(logn)\)完成查詢
給定多重集\(S=\{a_1,...,a_n\}\)的排列\(p_1,p_2,...,p_n\)
求該多重集有多少個本質不同的排列,字典序小於當前排列
答案對某給定常數\(m\)取模,不保證質數
首先,多重集合的排列數是
\[P=\frac{n!}{\sum (n_i!)} \]……
第二部分 數論初步
帶餘除法和整除
對於整數\(a,b\),存在唯一的兩個整數\(q,r\)使得:
\[b=aq+r(0\leq r \leq |a|) \]還有一種形式(恆等變換常用):
\[b=\lfloor\frac{b}{a}\rfloor\cdot a+b\%a \]還有一種形式(數論分塊常用):
\[r=b-\lfloor\frac{m}{j}\rfloor\cdot j \]當\(r=0\)時我們稱\(a\)整除\(b\),記作\(a|b\)
此時也稱\(b\)為\(a\)的倍數,\(a\)為\(b\)的約數
整除的性質
\[a|c,b|c,(a,b)=1 \Rightarrow ab|c \]\[a|bc,(a,b)=1 \Rightarrow a|c \]\[p|ab \Rightarrow p|a 或 p|b \]算數基本定理
任何一個自然數\(N\),可以唯一分解成有限個質數的乘積
\[N=\prod_{i=1}^m p_i^{k_i} \]其中\(p_1<p_2<p_3<...<p_n\)均為質數,指數\(k_i\)均為正整數
這樣的分解稱為標準分解式
通過標準分解式我們可以求出\(\sigma_0\)以及\(\sigma_1\),它們分別代表約數個數和約數和
下面給出公式
\[\sigma_0(x)=\prod_{i=1}^m(k_i+1) \]\[\sigma_1(x)=\prod_{i=1}^m\sum_{j=0}^{k_i}p_i^j \]以及等比數列求和公式
\[\sum_{i=1}^n a_i=\frac{a_0(1-b^n)}{1-b} \]素數無限定理
正整數集中包含無限個素數
證明:構造+反證法
複雜度常識
- 素數分佈
其中\(\pi(x)\)表示不大於x的素數個數
- 調和級數
- 素數調和級數
集合含義
\(A=p_1^{a_1}*p_2^{a_2}*...*p_n^{a_n},\;B=p_1^{b_1}*p_2^{b_2}*...*p_n^{b_n}\)
\[GCD(A,B)=\prod_{i=1}^mp_i^{min(a_i,b_i)} \]\[LCM(A,B)=\prod_{i=1}^mp_i^{max(a_i,b_i)} \]裴蜀定理
\(\forall a,b,d,(a,b)|d\;\)等價於\(\;\exists u,v\in N,ua+vb=d\)
用來判定二元一次方程無解
麥肯基定理:二元一次方程\(y=ax+b\)無非負數解最大整數為\(a*b-a-b\)
同餘
若\(a\equiv b(mod\;p)\),則存在整數\(k\)使得\(a=b+kp\)
\(a\equiv b(mod\;p)\Leftrightarrow p|b-a\)
\(a\equiv b(mod\; p),a\equiv b(mod\;p)\Leftrightarrow a\equiv b(mod\;[p,q])\)
\((k,p)=d,ka\equiv kb(mod\; p)\Leftrightarrow a\equiv b(mod\;\frac pd)\)
例題:遷徙
剩餘系
對特定的正整數\(p\),某個整數集中的所有數模\(p\)所得的餘數域
如果一個剩餘系中包含了這個正整數所有可能的餘數,就稱之為是模\(p\)的完全剩餘系
整數的所有運算限制在剩餘系中各種性質仍成立
簡化剩餘系
所有的\(n\)滿足\(0<n\leq p,(n,p)=1\)構成了模\(p\)的簡化剩餘系
記這樣\(n\)的個數為\(\phi(p)\)
若\((a,p)=1\),當\(x\)取遍膜\(p\)的簡化剩餘系時,\(ax\)也取遍膜\(p\)的簡化剩餘系
尤拉函式求逆元
最一般的情況:\(a^{\phi(p)-1}\equiv a^{-1}(mod\; p)\)
拉格朗日插值
定理:有n+1個點值可以求出一個n次多項式
基函式
\[L_i(x)=\begin{cases} 1,x=x_i\\ 0,x!=x_i \end{cases} \]拉格朗日插值
\[f(x)=\sum_{i=0}^ny_iL_i(x) \]\[L_i(x)=\prod_{j=0,j!=i}^n\frac{x-x_j}{x_i-x_j} \]兩式聯立得
\[f(x)=\sum_{i=0}^ny_i\prod_{j!=i}\frac{x-x_j}{x_i-x_j} \]即可以用來推出中國剩餘定理的一般解
離散對數
求解\(y^x=z(mod p)\)
雙向搜尋優化
即 BSGS演算法
板子:[SDOI2011]計算器
線性篩
完全積性函式
\[f(ab)=f(a)f(b) \]積性函式
\[f(ab)=f(a)f(b),(a,b)=1 \]不只是篩質數,凡是積性函式都可以用線性篩處理,比如說:
尤拉函式
莫比烏斯函式
約數函式組
\[\sigma_k(x)=\sum_{d|x}d^k \]具體的板子看這裡
注:第二部分有刪減,其中素數與合數、約束與倍數、歐幾里得演算法、擴充套件歐幾里得演算法、同餘方程、快速冪與快速乘、逆元、尤拉定理、線性求逆元、擴充套件尤拉定理、中國剩餘定理部分刪去,可以從本人以前的blog中獲得
第三部分 構造
- 在組合物件的變化過程中,尋找不變數
- 使用不等式匯出上下界 / 排除可能方案
- 根據同餘關係(尤其是奇偶性)判定某種型別組合物件不存在
- 多做題,自己體會
基於既定性質的構造
小題1
給定n個數,他們的和為M
你的任務是去掉一個數,然後把剩下的集合分成兩組
使得每一組內的和都\(\leq \lfloor\frac M2\rfloor\)
要求時間複雜度\(O(N)\),空間複雜度O(1)
從前往後掃,去掉剛好字首和大於等於\(\lfloor\frac M2\rfloor\)的元素,左右兩邊即為答案
小題2
給定一個長度為n的字串,字符集\(S=\{A,K,N,O,I\}\)
保證任意相鄰的三個字元必定不同
問是否存在長度至少為\(\lfloor\frac n3\rfloor\)的迴文子序列,有的話輸出任意一個
從兩邊開始分別往右和往左每3個分成一組,根據題意組裡不可能存在相同元素,那麼左邊的組和對應的右邊的組,至少有一個重複的字母,我們每次選兩邊對應的組中相同的元素,構造出來的就是長度至少為\(\lfloor\frac n3\rfloor\)的迴文子序列。
小題3
給定一張 3N 個點的圖
保證其中有一個大小為 2N 的團
找到一個大小為 N 的團
每次選兩個不連通的點,根據題意這兩個點最多有一個在團裡,那麼我們每次刪掉這兩個不連通的點,刪去了N對之後,最多有N個在團中的點被刪掉,最少有N個不在團裡的點被刪掉,剩下點的必定都在團裡
小題4
給定一張\(N(N\leq 10^5)\)個點的無向圖,在圖上放置監視哨,已知:
約定每個監視哨的勢力範圍為距離不超過 1 的所有點
保證圖中存在一個放置 K個監視哨的方案,使得所有點都被覆蓋
現在監視哨升級到勢力範圍為距離不超過 2 的所有點,請提供一個放置至多 K 個監視哨的覆蓋方案
我們把研究物件放到一條邊上,考慮開始的時候一條邊的端點放置監視哨的情況,
若兩個點都沒有放監視哨,情況不存在,
若一個點放置了監視哨,另一個沒有放,那麼在改變後還在那個位置放監視哨和之前情況等效,
若兩個都放了監視哨,那麼兩點覆蓋的範圍是對稱的,
所以我們的方案就是每次隨機選一個沒有被覆蓋的點,然後更新答案
增量法
小題1
給定\(n\),用\(1...n\)每個數恰好一次,使得算式的結果為24
可以使用四則運算和小括號
當\(n<4\)時,無解,當\(n=4\)和\(5\)的時候有特殊解,對於後面的情況,採用增量法,如果是奇數,對於\(n=5\)的情況在後面乘上\((i-(i-1))\),如果是偶數,在\(n=4\)的情況在後面乘上\((i(i-1))\)
小題2
漢諾塔,構造最少的移動次數,判斷當前一步具體的移動
我們知道次數的遞推式是\(f(n)=2*f(n-1)+1\),定義函式F(x,A,B,C),這裡的x指當前A柱頂端放的塊編號是多少,A指起始柱,B指輔助轉移的柱,C指目標柱,遞迴式就是
\[F(x,A,B,C)\Rightarrow\begin{cases} 輸出&x=f(i-1)+1\\ F(x,A,C,B)&x\leq f(i-1)\\ F(x-f(i-1),B,A,C)&x>f(i-1) \end{cases} \]小題3
對於所有\(1...n\)的排列,構造一個順序使得任意相鄰的兩個排列均可通過交換相鄰的一對位置得到
仍然採用遞迴,對於一組長度為\(n-1\)已經排好順序的排列,每個排列複製為\(n\)個,取每順序為奇數的排列從左往右在每個空隙插入\(n\)這個數,取每順序為偶數的排列從右往左在每個空隙插入\(n\)這個數,得到的新的排列組就是長度為\(n\)時的答案,此外有一個很有意思的發現,就是新加入的數在排列組中是呈S型走向的
小題4
對於所有長度為\(n\)的\(2^n\)的\(01\)串,構造一個迴圈的順序使得任意相鄰的兩個串只有一位不同
可以把題目轉換成超立方體的遍歷問題,因為超立方體上每一個相鄰的點座標都只有一位不同,對於一個\(n\)維立方體,我們可以把它轉化成\(n-1\)維來處理,首先,一個\(n\)維立方體,是由兩個\(n-1\)維立方體拼成的,那麼
當起點和終點在同一個\(n-1\)維立方體上,我們直接遍歷兩個立方體,在關鍵立方體上拆除一條邊連上另外一個立方體,
當起點和終點不在同一個\(n-1\)維立方體上,在一個立方體中走到\(x\)點轉移到另一個立方體上再走到終點,\(x\)點任選
小題5
給定\(n\)個互不相同的單詞,第\(i\)個單詞將要在文章中出現\(a _ i\)次
現在將單詞重編碼為二進位制串,需保證文章不出現歧義
最小化文章總長度
Bonus: 重編碼為\(k\)進位制
構造二叉哈夫曼樹,求樹的帶權路徑長度
具體看這裡
基於圖論的構造
小題1
給定一個序列\(\{d_i\}\),判斷是否能構造一個簡單無向圖,給出方案
Havel定理告訴我們,可以先按大小對序列排序,然後讓d最大的點與次大的d個點分別連邊,不斷操作,直到建出完整的圖,或是出現負數
歐拉回路
需熟練掌握歐拉回路(通路)的判定、構造和性質
歐拉回路:一個環經過所有邊
判定
迴路 | 通路 | |
---|---|---|
無向圖 | 圖中所有點均為偶點 | 圖中只有2兩個奇點 |
有向圖 | 底圖連通,各點出入度相同 | 底圖連通,只存在兩個點出入度有差異,且差為1 |
構造
通過判定可構造
性質
深刻的組合性質:每個點出入平衡,每條邊恰好被訪問一次
哈密頓迴路
沒有比較好的判定定理,但是
對於一個競賽圖,一定有哈密頓通路
對於一個強連通競賽圖,一定有哈密頓迴路
競賽圖縮點後肯定是一條鏈
掌握特殊情況下的求法即可
哈密頓迴路:一個環經過所有點
競賽圖:有向完全圖(底圖是完全圖的有向圖)
n階競賽圖:底圖是n階完全圖的有向圖
求強聯通競賽圖的哈密頓迴路
第一步,先找到一條哈密頓通路,具體做法類似連結串列
- 找到一條點指向鏈頭,把它插入到鏈頭
- 找到一條點被鏈尾指向,把它插到鏈尾
- 找到一條點被鏈中的一個點指向並指向原鏈中的下一個點,把它插到兩點之間
第二步,由通路變回路
從鏈尾開始掃,掃到第一個指向鏈頭的點,得到一條不完整環
然後搞一堆騷操作就整好環了
//這個部分最好畫圖理解,圖是一個環長一條尾巴的樣子。
r=0;//這裡是緊接著上面找完通路。先把r置為0表示還沒有找到初始的環。
for(int i=l;i;i=nxt[i])//r是環上最靠近鏈的點,r->l是環上的邊,r->nxt[r]是鏈上的邊。
if(r){//嘗試在環中插入點i
for(int j=l,k=r;;k=j,j=nxt[j]){
if(a[i][j]){//在環上找到一個可以作為nxt[i]的點。
nxt[k]=nxt[r];//j作為了i的後繼,那麼本來j的前驅k就要另找一個後繼了。(這裡注意nxt[r]不一定是i,因為可能前面的一些點沒有插入成功)
if(k!=r)nxt[r]=l;//本來沒有連上的環上的邊要連上(k=r的話r的後繼在上一句話已經改了,不是l了)
l=j,r=i;break;//根據l和r的定義修改l和r
}
if(j==r)break;//確實有可能當前無法插入,但是後面的點一定會有插入成功的,那時這個點也就會進入環內。
}
}
else if(a[i][l])r=i;//這裡找到了初始的環
nxt[r]=l;//這裡把最後一條邊連上。
求競賽圖的哈密頓迴路
縮點後是個環,每個強連通分量都有一個環,都接起來就行了
void work() {
int l = r = 1;
for (int i = 2;i <= n; i++) {
if (ed[i][l]) nxt[i] = l, l = i;
else if (ed[r][i]) nxt[r] = i, r = i;
else for (int j = l; ; j = nxt[j])
if (a[i][nxt[j]]) { nxt[i] = nxt[j], nxt[j] = i; break; }
}
}
稠密圖的哈密頓迴路
不會