日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

算法笔记(自用)

發布時間:2023/12/10 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 算法笔记(自用) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

C++ Basic

C++基礎/基本注意點

類型范圍

int -2147483648~2147483647

long -2147483648~2147483647

long long -9223372036854775808~9223372036854775807 (別用cin,用scanf(“%lld”)) (2^64)

有時候long long 過不了可以用long double

常用定義

正無窮 0x3f3f3f3f

? 正無窮的初始化

? 使用<cstring>頭文件:memset(v,0x3f,sizeof v) //將v的值初始化為無窮大0x3f

注意:使用memset初始化只能初始化0,-1,0x3f; 若要初始化其他值,使用fill()

fill(a,a+n,val); or fill(a.begin(),a.end(),val); in stl

數據輸入輸出

  • 數據規模大的使用 scanf 讀取 printf 輸出

  • 輸出固定n位數(不足補0)

    printf("%05d",x) //輸出五位整數,不足補0

  • 關于字符串的處理(帶空格)

    getline(cin,str); //注意前面如果按了回車一定要getchar()一下!!或者cin.ignore()

    連續讀取帶所需數字的字符串如12 is the root of 35,可以使用sscanf

    sscanf(str.c_str(),"%d is the root of %d",&a,&b);

    而sprintf可以把數字或者其他寫入字符數組中

    char res[5]; sprintf(res,"%04d",num);
  • 小數位保留

    直接 printf(%.xlf”,xxx);就能保留小數點后幾位round(x);作用是對小數點后一位進行四舍五入,例如 1.5->2.0 ; 1.56->2.0

轉義

? %的轉義是%%

位運算

  • 除以2的位運算:>>1,例如:(a+b)/2寫成a+b>>1,a/=2寫成a>>=1;同理,乘以2就是<<1(注意:只能對整數操作)

  • 不等于-1,例如i!=-1的快速寫法~i

  • 對2取模,簡寫成x&1 例如:if(x%2==1)改寫成if(x&1==1)

一些常用的位運算

  • 求n的二進制中第k位(k=0是個位): n>>k&1
  • lowbit:返回x的最后一位1,應用:求n的二進制中1的個數
  • int lowbit(int x){return x&-x; //或者x&(~x+1) } int res=0; while(x){x-=lowbit(x);res++; } cout<<res;

    浮點數的比較方法

    由于計算機中浮點數的存儲問題,不能直接==,需要判斷他們相減的值是不是小于某個很小的數,加很多括號是為了防止宏定義出錯

    const double eps = 1e-8; #define Equ(a, b) ((fabs((a) - (b))) < (eps)) #define More(a, b) (((a) - (b)) > (eps)) #define Less(a, b) (((a) - (b)) < (-eps)) #define LessEqu(a, b) (((a) - (b)) < (eps)) if(Equ(a, b)) {cout << "true"; }

    圓周率 Pi

    const double Pi = acos(-1.0);

    數組復制

    #include<cstring>

    memcpy(a,b,sizeof b); //把b數組復制到a數組中

    結構體

    結構體可以{aa,bb,cc}這樣寫,例如:

    struct Person {string name;int age,w; } vector<Person> ages[N]; int main(){char name[10];int age,w;scanf("%s%d%d",name,&age,&w);ages[age].push_back({name,age,w}); //將結構體插入ages容器數組中,可以使用類似pair的寫法 }//結構體中也可以定義方法 struct Student(){Student(){} //構造函數Student(string _id):id(_id){ //帶參數的構造函數for(int i=1;i<=k;i++) grade[i] = -2;total = cnt = 0;}void calc(){ //自定義計算方法for(int i=1;i<=k;i++){total += max(0,grade[i]);if(grade[i]==p_score[i]) cnt++;} }bool has_submit(){ for(....)....return false;}bool operator< (const Student& t) const {//運算符重載.... return ....} }

    STL 操作

    注意事項

    • xxx的最后一個元素的迭代指針是xxx.end()-1
    • 如果要重復用記得clear()一下…

    向量 vector

    • vector找最值和對應位置,使用中的min_element()和max_element(),返回最值的迭代器
    auto pos = min_element(v.begin(),v.end())max_element(v.begin(),v.end()) 對應位置是pos-v.begin();值是*pos
    • 建立一個逆序vector

      vector<int> B(A.rbegin(),A.rend())

    • 遍歷vector

    vector<Person> ages[N]; for(auto &age:ages) sort(age.begin(),age.end());
    • 找一個元素

      v.find(key) 返回下標指針

    • 去重

      unique()需要和``v.erase()`配合才是真正的去重

      v.erase(unique(v.begin(),v.end()),v.end());
    • 初始化二維vector固定大小

      //例如在固定大小的地圖中 int row = grid.size(); int col = gird[0].size(); vector<vector<int>> dp(row,vector<int>(col,0));
    • push_back()和emplace_back()

      emplace_back() 和 push_back() 的區別,就在于底層實現的機制不同。

      push_back() 向容器尾部添加元素時,首先會創建這個元素,然后再將這個元素拷貝或者移動到容器中(如果是拷貝的話,事后會自行銷毀先前創建的這個元素);而 emplace_back() 在實現時,則是直接在容器尾部創建這個元素,省去了拷貝或移動元素的過程。

    映射 map

    • 查找這個map中有沒有這個key:m.count(key)

    unordered_map是沒自動排序的map,此時通過max_element()找到的最大值,他的key不一定是最小的,若是map則兩個相同的值中,找到的key是最小的。

    • 找到Map中最大的value對應的key

    使用max_element() 配合cmp()函數中使用pair組成鍵值對

    bool cmp(const pair<type1,type2> left,const pair<type1,type2> right){return left.second<right.second; }
    • 對map的排序

    對map排序實際上還是要轉換在vector中進行,重載比較函數

    集合 set

    添加

    s.insert(key)

    刪除

    s.erase(key或者iterator)這里參數可以是值也可以是位置指針

    注意,在multiset中erase一個數的話會把set里的數全刪除,刪一個數使用s.erase(s.find(x))

    優先隊列 priority_queue

    定義

    priority_queue<type,container,function>

    使用優先隊列實現堆

    • 小根堆:priority_queue<PII,vector<PII>,greater<PII>> heap PII是pair經過typedef
    • 大根堆:priority_queue<PII,vector<PII>,less<PII>> heap

    字符串 string

    • 刪除特定字符

      使用find()和erase()

    • 大小寫轉換

      遍歷字符串,對每個字符使用頭文件中的tolower(),toupper()

    • 字符串轉數字、數字轉字符串

      數字 --> 字符串 :to_string()

      字符串 --> 數字:

      1. 整數 stoi()2. 浮點,使用 atof(str.c_str()) //這里要傳char* 也可以 stof() 和 stod()
    • 找到對應子串位置

      str.find("substr...");//如果沒找到返回-1,找到返回其下標
    • 在特定位置插入字串

      s.insert(pos,size,char);

      例如在字符串開頭插入n個0s.insert(0,n,'0')

    • sscanf(),sprintf()從字符串中讀入數據和把數據寫入字符串中

    • string char[]、char*的轉換關系:

      • string -> const char*使用c_str()

      • char*和char[]可以直接轉string,如:string s; char* p="hello"; s=p;

    • 帶空格的字符串按空格分割,使用stringstream

      #include<sstream>vector<string> res; //存放分割后字符串string words="ACD BCD CCD"; string word; stringstream input(words); while(input>>word){ res.push_back(word); cout<<word<<endl;}

    關于

    isalnum(char c)判斷該字符是不是字符數字或者字母,是返非0,否則為0還有isupper(),islower()等

    隊列queue、棧stack

    他們不支持clear()操作

    • queue::emplace()

      將元素插入隊列末尾

    • queue::push()

      插入隊尾

    • queue::pop()

      隊頭出隊

    關于permutation

    • is_permutation(v1.begin(),v1.end(),v2.begin())判斷v2是不是v1的序列,是1否0

    • next_permutation和prev_permutation用法

      string s = "12345"; do{cout<<s<<endl; }while(next_permutation(s.begin(),s.end()));string s = "12345"; do{cout<<s<<endl; }while(prev_permutation(s.begin(),s.end()));

    排序

    注意要穩定排序有個stable_sort()

    1. 默認函數greater<type>()和less<type>()

    sort的排序默認從小到大遞增排列,也可以增加第三個參數傳入一個比較函數,C++提供兩個默認的比較函數

    greater<>() 從大到小,遞減排列

    less<>() 從小到大,遞增排列(默認)

    注意:在創建map<>,set<>等STL類型時,通常可以傳入比較函數改變其默認排序

    快排模板

    void quick_sort(int q[],int l,int r){if(l>=r) return;int x = q[l],i=l-1,j=r+1;while(i<j){} }

    2. 使用運算符重載

    運算符重載可以放到結構體中

    //例如在Person結構體中利用運算符重載排序,要求按資產w降序,否則年齡age升序,其次姓名name升序 struct Person{string name;int age,w;//年齡和資產bool operator< (const Person &t) const//重載小于號{if(w!=t.w) return w > t.w; //先按資產排序,資產不同則資產大的排在前面if(age!=t.age) return age < t.age; //否則年齡不同的話,年齡小的排在前面return name < t.name; //最后姓名字典序小的排在前面} //注意后面使用 < 的時候,若 a<b 說明a的資產更大,年齡更小,姓名字典序更小 }

    3. 使用lamda表達式

    sort(a,a+n,[](int x,int y){if(cnt[x]!=cnt[y]) return cnt[x]>cnt[y];return x<y; }); //也可以匿名函數 auto cmp = [](int x,int y){if(cnt[x]!=cnt[y]) return cnt[x]>cnt[y];return x<y;};

    4. 手寫cmp()函數

    bool cmp(const int a,const int b){ //若e[a]<e[b] 則a排在b的前面if(e[a]!=e[b]) return e[a]<e[b];return e[a]>e[b]; }

    排序算法操作

    K路歸并

    標準:利用堆找每一路最大/小

    其次:使用遍歷找

    開個vector數組,然后把數組中的vector都排好序,利用遍歷找最小

    //遍歷找最小模板 int t = -1; for(int i=a,a<=b;i++){if(t==-1||ages[t][idx[t]]<ages[i][idx[i]])t=i; } //for結束后,t就是最小值的下標 while (true) {d<<=1;bool eq = match();for (int i = 0; i < n; i += d) {sort(a + i, a + min(n,i + d)); //這里間隔d個排序為什么用min,因為最后面不夠d個只能到n了}if (eq) break; }

    關于排序題的排名輸出(相同分數要求排名一樣)

    int rank = 1; for(int i=0;i<list.size();i++){if(i&&grade[i]!=grad[i-1]) rank=i+1; //如果成績不等rank就是i+1;//輸出信息... }

    一些題型套路

    • 推理題都是用枚舉來做
    • 鏈表題都丟到數組里做

    基本算法技巧

    雙指針

    一般分兩大類,一類是兩個指針分別指向兩個序列,一類是兩個指針指向一個序列

    一般都是先把暴力算法寫出來,然后看有沒有單調性,有就用雙指針優化

    核心思想

    單調性,將雙重循環優for(i:n){for(j:n){}}化到O(n)

    基本模板

    for(int i=0,j=0;i<n;i++){ while(j<n&&check(i,j)) j++; //然后是具體邏輯}

    離散化

    用于要使用值做下標的情況下,值域非常大,但是個數不多的時候,把序列映射到從0開始的連續自然數

  • 若序列中有重復元素則需要去重
  • 需要能快速算出序列中的值離散化后的值(二分)【起始也可以排序后用一個unordered_map記錄位置】
  • //1. 把所有要用到的值加入一個數組中 //2. 對數組進行排序去重 all.erase(unique(all.begin(),all.end()),all.end()); //3. 遍歷一次記錄對應位置關系 unordered_map<int,int> pos; for(int i=1;i<=all.size();i++) pos[all[i-1]]=i; //如果要用到前綴和就從1開始映射//4. 之后每次要用到原本的值,就套一個pos[x]

    基礎算法模板

    二分

    左閉右閉,找是否存在滿足條件的元素,沒找到返回-1

    int BS(int l,int r,int key){ while(left<=right){ int mid = l+r>>1; //防止溢出可以mid=left+(right-left)/2 if(A[mid]==key) return mid; else if(A[mid]>key) r = mid-1; else l=mid+1; } return -1;}

    第一個滿足條件的元素位置【該條件一定是從左到右先不滿足,然后滿足】

    int BS(int l,int r){ while(l<r){ int mid = l+r>>1; if(check(mid)) r=mid; else l=mid+1; } return l;}

    找第一個不滿足性質的元素位置【從左到右滿足,然后不滿足】

    int BS(int l,int r){ while(i<r){ int mid = l+r+1>>1; if(check(mid)) l=mid; else r=mid-1; } return l;}

    DFS、回溯枝剪

    深搜關鍵在于順序

    關于n皇后的技巧性(數組設置)

    int row[N],col[N],dg[N],udg[N]; //這里col[i]表示第i行是否有皇后,dg和udg有坐標變換u-i+n和u+i//即dg[x+y]表示該斜是否有皇后,udg[x-y+n]表示另一斜是否有皇后 //dfs 原始思維 O(2^(n^2)) void dfs(int x,int y,int s) //x表示行,y表示列,s是皇后個數 { if(y==n) y=0,x++; //列越界,跳至下一行 if(x==n) { //到達最后一行 if(s==n){ for(int i=0;i<n;i++) puts(g[i]); puts(""); } return; } dfs(x,y+1,s); //不放皇后 if(!row[x]&&!col[y]&&!dg[x+y]&&!udg[x-y+n]){ g[x][y] = 'Q'; //記錄 row[x] = col[y] = dg[x+y] = udg[x-y+n] = true; dfs(x,y+1,s+1); row[x] = col[y] = dg[x+y] = udg[x-y+n] = false; //回溯狀態恢復 g[x][y] = '.'; }}//dfs 全排列思想 O(n!)void dfs(int u) //第u行{ if(u == n){ //到達最后一行 for(int i=0;i<n;i++) puts(g[i]); puts(""); return; } for(int i=0;i<n;i++) //遍歷i列和對應斜線 if(!col[i]&&!dg[u+i]&&!udg[n-u+i]){ g[u][i] = 'Q'; col[i] = dg[u+i] = udg[n-u+i] = true; dfs(u+1); col[i] = dg[u+i] = udg[n-u+i] = false; //回溯狀態恢復 g[u][i] = '.'; } }

    高精度加法(大數加法)

    //C = A + B, A>=0,B>=0 這里就是把A和B的數字相加放到C中 //注意:這里 A,B,C 都是倒序的 比如[1,5] + [1,6] = [2,1,1] <-- 51 + 61 = 112 vector<int> add(vector<int> &A,vector<int> &B){ if(A.size()<B.size()) return add(B,A); //保證A是位數多的 vector<int> C; int t = 0; //保存每一位相加結果 for(int i=0;i<A.size();i++){ t += A[i]; if(i<B.size()) t+=B[i]; C.push_back(t%10); t /= 10; } if(t) C.push_back(t); //如果t還有進位,再push進去 return C; }

    數據結構相關算法

    鏈表

    存儲

    //數組模擬鏈表 int head,e[N],ne[N],idx; //head頭節點下標,e是節點值, //ne是節點的下一個節點下標,idx存儲用到了哪個地址

    初始化

    void init(){head=-1;idx=0; }

    插入

    //頭插 void add(int x){}

    一些鏈表題做法

    先存下鏈表,然后遍歷下鏈表存在數組中,在數組中進行操作后再把數組變鏈表

    并查集

    基本操作

    int find(int x){ if(p[x]!=x) p[x]=find(p[x]); return p[x];} //路徑壓縮find //初始化操作 for(int i=1;i<=n;i++) p[i]=i; //合并操作 pa = find(pa), pb = find(pb); if(pa!=pb) p[pa] = pb; //并查集中找代表節點,需要通過find()來找
    • 統計每個集合中人數

      int cnt[N];for(int i=0;i<N;i++) cnt[find(i)]++;//若要再統計集合個數int k=0;while(cnt[k]) k++;
    • 統計集合個數(連通塊個數)
      可以掃描一遍p數組,如果p[i]==i,則集合數量++;

    • 維護根結點為點權最大的

      //把點權最大的作為根結點 for(int i=1;i<idx;i++){ int pi = find(i); if(v[i]>v[pi]){ p[i]=i; p[pi]=i; } }

    存儲

    數組模擬鄰接表一定要記得h初始化為-1,并且要記得e[M],ne[M]設置成邊數的個數,不然容易段錯誤

    //鄰接矩陣int g[N][N]; //鄰接表1. 使用vectorvector<int> g[N];g[ver].push_back(b); //加邊2. 使用數組模擬int h[N],e[N],ne[N],w[N],idx; //結點地址,下一個邊,下一個邊的結點地址,x到y邊的權重,idxmemset(h,-1,h); //將鄰接表表示節點地址的h初始化為-1 void add(int x,int y,int z){ //鄰接表加邊模板 e[idx]=y, w[idx]=z, ne[idx]=h[x],h[x]=idx++; //y插在對頭。} //使用鄰接表時的遍歷邊 for(int i=h[ver];i!=-1;i=ne[i]){ ...}

    遍歷

    DFS

    int dfs(int u){ st[u] = true; for(int i=h[u];~i;i=ne[i]){ int j=e[i]; if(!st[j]) dfs(j); } }

    BFS

    queue<int> q; st[root] = true; q.push(root); while(q.size()){ int t = q.front(); q.pop(); for(int i=h[t];~i;i=ne[i]){ int j=e[i]; if(!st[j]){st[j]=true; q.push(j); } } }

    一層一層遍歷的BFS

    //例如計算前L層中節點個數(若根結點為0層的話) bool st[N]; int bfs(int s){ memset(st,0,sizeof st); queue<int> q; q.push(s); st[s] = true; int res = 0; //結點個數 for(int step=0;step<=L;step++){ //遍歷L層 int sz = q.size(); //目前層數中結點個數 res += sz; for(int i=0;i<sz;i++){ //遍歷當前層數中所有點 int t = q.front(); q.pop(); for(int j=h[t];~j;j=ne[j]){ int k = e[k]; if(!st[k]){ st[k] = true; q.push(k); } } } } return res; //包括根節點}//抽出模板來就是: for(int step=0;step<Layer;step++){ int sz=q.size(); ... for(int i=0;i<sz;i++){ int t = q.front(); q.pop(); for(int j=h[t];~j;j=ne[j]){ int k = e[k]; int(!st[k]){ st[k]=true; q.push(k); } } } } //或者加一層layer把,然后和常規一樣了int layer[N];

    判斷連通

    1. 使用DFSbool vis[N]; //表示結點是否被訪問 void dfs(int u){ vis[u] = true; for(int i=1;i<=n;i++){ if(!vis[i]&&g[u][i]) dfs(i); } } 2. 使用BFS int q[N],vis[N]; void bfs(int u){ int hh=0,tt=0; q[0] = u; vis[u] = true; while(hh<=tt){ int k = q[hh++]; vis[k] = true; for(int i=1;i<=n;i++) if(!vis[i]&&g[k][i]) q[++tt] = i; } } for(int i=1;i<=n;i++){ if(!vis[i]) return false; //說明不連通 } 3. 使用并查集 int p[N]; int find(int x){ if(x!=p[x]) p[x] = find(p[x]); return p[x]; } for(int i=1;i<=n;i++) p[i] = i; int cnt = n;

    應用 – 最短路

    Dijkstra(只能處理邊權為正數的問題)

    //樸素Dijjkstra O(n^2) int dist[N]; //每個點到起點的距離 int d[N][N]; //鄰接矩陣,記得也要初始化正無窮 bool st[N]; //存儲每個點的最短距離是否已經確定 void dijkstra(){ memset(dist,0x3f,sizeof dist); //dist初始化 dist[S] = 0; //起點S置0 for(int i=0;i<n;i++){ //暴力尋找距離最近的點 int t = -1; for(int j=1;j<=n;j++){ if(!st[j]&&t==-1||(dist[j]<dist[t])) t = j; } st[t] = true; //找到最小后進行標記 for(int j=1;j<=n;j++){ //更新各個節點距離,這里是遍歷邊的數量m! dist[j] = min(dist[j],dist[t]+d[t][j]); //如果還有其他值需要進行判定更新可以在這里寫 } } }//堆優化 O(mlogn)//一般用在稀疏圖,用鄰接表存,用鄰接表時重邊就無所謂了 typedef piar<int,int> PII; //節點距離和結點編號組成pair priority_queue<PII,vector<PII>,greater<PII>> heap; //優先隊列模擬小根堆 int h[N],e[N],ne[N],w[N],idx; //鄰接表實現圖 int dist[N],pre[N];bool st[N]; //判斷該點是否已經存在集合中 memset(h,-1,sizeof h); void dijkstra(int S,int D){ heap.clear(); memset(dist,0x3f,sizeof dist); memset(st,false,sizeof st); dist[S] = 0; heap.push({0,S}); while(heap.size()){ auto t = heap.top(); heap.pop(); int ver = t.second, distance = t.first; if(st[ver]) continue; //該結點已經在集合中了,直接看下一個 st[ver] = true; //把該結點加入集合 for(int i=h[ver];~i;i=ne[i]){ int j = e[i]; if(dist[j] > distance + w[i]){ dist[j] = distance + w[i]; heap.push({dist[j],j}); pre[j] = ver; }else if(dist[j]==distance+w[i] && ...){ //第二判優條件 ... }} } if(dist[D]=0x3f3f3f3f) return -1; else return dist[D]; } //遍歷pre生成路徑 vector<int> path; for(int i=D;i!=S;i=pre[i]) path.push_back(i); cout<<S; for(int i=path.size()-1;i>=0;i--) cout<"->"<<path[i];cout<<endl;

    Dijkstra+DFS

    //有些條件無法再dijkstra中滿足,需要通過dfs遍歷所有路徑判斷 //先在dijkstra中存下最短路 typedef pair<int,int> PII; priority_queue<PII,vector<PII>,greater<PII>> heap; int h[N],e[N],ne[N],w[N],idx; int dist[N]; bool st[N]; int c[N]; //假設這個是第二判優值 vector<set<int>> pre; void dijkstra(int S){ memset(dist,0x3f,sizeof dist); memset(st,0,sizeof st); dist[S] = 0; heap.push({0,S}); while(heap.size()){ auto t=heap.top(); heap.pop(); int ver=t.second,d = t.first; if(st[ver]) continue; st[ver]=true; for(int i=h[ver];~i;i=ne[i]){ int j = e[i]; if(dist[j]>d+w[i]){ dist[j] = d+w[i]; pre[j].clear(); pre[j].insert(ver); heap.push({dist[j],j}); }else if(dist[j]==d+w[i]){ pre[j].insert(ver); heap.push({dist[j],j}); } } } }//遍歷所有最短路,生成最優路 //要求: //1. 一個全局最優值 optValue //2. 記錄最優路徑的path數組 //3. 臨時記錄dfs路徑的tmp void dfs(int v){ //當前結點 if(v==st) //如果到了起點(葉子節點) { tmp.push_back(v); int value=0; for(int i=path.size()-1;i>0;i--)//這里遍歷n-1條邊,計算值是否是最優值 ... if(value優于optValue) {path = tmp; tmp.pop_back(); return; } tmp.push_back(v); for(int i=h[v];~i;i=ne[i]){ int j=e[i]; dfs(j); } tmp.pop_back();} }

    SPFA(可以處理負邊權)

    //用到bfs和隊列,挺像dij#include<queue>bool st[N]; //標記該點是否在隊列int spfa(int S){ queue<int> q; memset(dist,0x3f,sizeof dist); memset(st,false,sizeof st); dist[S]=0; q.push(s); st[S] = true; while(!q.empty()){ int t = q.front(); q.pop(); st[t] = false; for(int i=h[t];~i;i=ne[i]){ int j = e[i]; if(dist[j]>dist[t]+w[i]){ dist[j]=dist[t]+w[i]; pre[j]=t; if(!st[j]) { q.push(j); st[j] = true; } } } } return dist[D]; //返回目的地距離}if(spfa()==0x3f3f3f3f) //不可到達else ...

    SPFA判斷負環

    一開始把所有點加入隊列,判斷最短路上的點數是否>=n,是則出現負環

    int dist[N],cnt[N]; //cnt是最短路中經過的點數 bool spfa(){ for(int i=1;i<=n;i++){ q.push(); st[i]=true; } while(q.size()){ int t=q.front(); q.pop(); st[t]=false; for(int i=h[t];~i;i=ne[i]){ int j=e[i]; if(dist[j]>dist[t]+w[i]){ dist[j]=dist[t]+w[i]; cnt[j]=cnt[t]+1; if(cnt>=n) return true; if(!st[j]){ q.push(j); st[j]=true; } } } } return false; }

    Floyd(多源最短路)

    int dis[N][N]; for(int k=0;i<n;k++) //遍歷所有邊找k for(int i=0;i<n;i++) for(int j=0;j<n;j++) if(dis[i][k]<INF&&dis[k][j]<INF&&dis[i][k]+dis[k][j]<dis[i][j]) dis[i][j] = dis[i][k]+dis[k][j];

    拓撲排序

    //使用BFS int h[N],e[N],ne[N],idx; //記得h一開始memset成-1 int q[N],d[N]; //d表示該點的入度 bool toposort(){ int hh=0,tt=-1; //這里因為沒有把對頭q[0]初始化,所以tt=-1 for(int i=1;i<=n;i++) //遍歷1~n個結點 if(!d[i]) q[++tt]=i; //把入度為0的點加入隊列 while(hh<=tt){ int t = q[hh++]; for(int i=h[t];~i;i=ne[i]){ int j=e[i]; if(--d[j]==0) q[++tt] = j; //這里要==0,因為有可能會出現負數 } } return tt==n-1; //如果無環則隊列中有n個結點,否則比n少 } //輸出拓撲序 for(int i=0;i<n;i++) cout<<q[i]<<' ';

    判斷是否為拓撲排序

    由于拓撲排序在前面的點的下標一定小于后面的點,遍歷每條邊,看起點下標是否小于終點下標

    超級源點和超級匯點

    有時候有多個出發點,比如多個入度為0的,這時候需要一個虛擬起點連接所有真正的起點

    DAG最長路(關鍵路徑)

    有三種方法做:

  • 將邊權取相反數,通過SPFA做(先通過拓撲排序判斷一下有無環)
  • 使用e[N],l[N],ve[N],vl[V],w[N]的關鍵路徑求解做法
  • 使用DP,DP[i][j]表示i到j的最長路
  • 通過兩個遍歷建樹

    中序+前/后序

    int l[N],r[N],pos[N]; //l,r是左右孩子,pos是節點在中序遍歷中對應位置int in[N],pre[N]; //或者是postorder,反正一定要有一個中序遍歷 int build(int il,int ir,int pl,int pr){ int root = pre[pl]; //如果是后序遍歷就是post[pr] int k = pos[root]; if(il<k) l[root] = build(il,k-1,pl+1,pl+k-il); //后序是build(il,k-1,pl,k-il+pl-1) if(ir>k) r[root] = build(k+1,ir,pl+k-il+1,pr); //build(k+1,ir,k-il+pl,pr-1) return root; }

    中序+層序

    思路:先求出先序,然后通過先序和中序建樹

    int layer[N],in[N],pre[N]; int l[N],r[N],pos[N]; int idx; void makepre(int il,int ir,int ll,int lr){ int i=0,j=0; for(i=ll;i<=lr;i++){ bool find=false; for(j=il;j<=ir;j++) if(l[i]==in[j]){ pre[idx++]=in[j]; //構造先序 find=true; break; } if(find) break; } if(j>il) makepre(il,j-1,ll,lr); //ll和lr是一直不變的 if(j<ir) makepre(j+1,ir,ll,lr); } int build(int il,int ir,int pl,int pr){//這個就和上面的一樣了}

    層序遍歷

    //借助隊列 void bfs(int root){ //單獨遍歷一層的辦法 q[0]=root; int hh=0,tt=0; while(hh<=tt){ int head=hh,tail=tt; //這里表示該層的頭和尾 while(hh<=tail){ //對這一層處理,加入隊列 int k = q[hh++]; if(l[k]) q[++tt] = l[k]; if(r[k]) q[++tt] = r[k]; } } }

    完全二叉樹

    存儲

    使用一維數組存取層序遍歷,一個點x左兒子2x,右節點2x+1,父節點x/2下取整

    給定值,建立完全二叉搜索樹

    排序得到中序遍歷,然后按中序遍歷下樹,邊遍歷邊填數

    判斷是否為完全二叉樹

    //通過遞歸把節點值放入數組中,放完最大下標為n則是完全二叉樹【在二叉樹中叫做先序遍歷】 int maxk; void dfs(int u,int k){ if(maxk<k) maxk = k; if(l[u]!=-1) dfs(l[u],k*2); if(r[u]!=-1) dfs(r[u],k*2+1); }//使用bfs判斷 int q[N]; bool bfs(int u){ int hh=0,tt=0; q[0] = u; pos[u] = 1; //存放節點對應位置 while(hh<=tt){ int k = q[hh++]; if(pos[u]>n) return false; if(l[k]) q[++tt] = l[k], pos[l[k]] = pos[k]*2; if(r[k]) q[++tt] = r[k], pos[r[k]] = pos[k]*2+1; } return true; }

    二叉排序樹 BST

    插入

    void insert(int &u,int w){ if(!u) u=++idx,v[u] = w; //如果節點不存在,分配一個地方存 else if(w<v[u]) insert(l[u],w); else insert(r[u],w);}

    刪除

    int findMin(int root){ while(r.count(root)) root = r[root]; return root;}int findMax(int root){ while(l.count(root)) root = l[root]; return root;}int del(int u){ }//如果是葉節點,直接刪除//否則刪除其中序遍歷下的前驅/后繼

    平衡二叉樹 AVL

    int l[N],r[N],v[N],h[N],idx; //左孩子,右孩子,節點值,節點高度,當前用的節點void update(int u){ //調整節點高度 h[u] = max(h[l[u],h[r[u]])+1; }int get_balance(int u){ return h[l[u]]-h[r[u]];} //左子樹高度減右子樹高度int R(int &u){ //右旋 int p = l[u]; l[u] = r[p],r[p] = u; update(u),update(p); u = p;}int L(int &u){ //左旋,和右旋是對稱的 int p = r[u]; r[u] = l[p],l[p] = u; update(u),update(p); u = p;}void insert(int &u,int w){ if(!u) u =++idx,v[u] = w; else if (w<v[u]){ insert(l[u],w); if(get_balance(u)==2){ if(get_balance(l[u])==1) R(u); else L(l[u]),R(u); } } else{ insert(r[u],w); if(get_balance(u)==-2){ if(get_balance(r[u]==-1)) L(u); else R(r[u]),L(u); } } update(u);}

    記憶化搜索(樹形dp)

    int f[N],p[N]; //比如:f存儲到根節點的距離,p[u]是表示u節點的父節點 都初始化為-1int dfs(int u){ if(f[u]!=-1) return f[u]; //f[u]已經存在了,不再搜索 if(p[u]==-1) //是根節點 return f[u] = 0; return f[u] = dfs(p[u]) + 1; }

    最低公共祖先 (LCA)

    爬山法

    思路:在下面的節點一直移動到和另一個同一層開始,一起向上爬,直到相等 時間復雜度:O(h)
    需要優化常數:把int范圍內的數映射到0~N-1 如把最小的數映射到0,第二小的數映射到1…(離散化) 這種沒說數據范圍的就需要離散化,或者你的n開無敵大。。int范圍內

    int h[N],p[N]; //求LCA中要用到兩個信息:節點深度和節點父節點int pre[N],in[N]; //存離散化后的值int seq[N]; //存原本的序列unordered_map<int,int> pos; //存儲離散化后的位置//離散化,要取原本的值就是seq[i]for(int i=0;i<n;i++){ pos[seq[i]] = i;}//二叉樹重建int build(int il,int ir,int pl,int pr,int depth) //由于這里只要父節點和高度的信息,只用h和p,參數傳入深度{ h[root]=depth; ...}//爬山法int x=a,y=b;while(a!=b){ if(h[a]<h[b]) b=p[b]; //b在下面,b上去 else a=p[a];}if(a!=x&&b!=y){ }else if(a==x&&b!=y){ }else if(a!=x&&b==y){ }else{ //a==x&&b==y }

    Huffman – 參照貪心

    概念

    大根堆:父節點的值大于等于子節點

    小根堆:父節點的值小于等于子節點

    判斷是不是堆(是什么堆)

    bool lt = false, gt = false;for (int i = 1; i <= n; i ++ ) for (int j = 0; j < 2; j ++ ) //遍歷左右孩子 if (i * 2 + j <= n) { //如果左右孩子合法 if (h[i]<h[i*2+j]) lt=true; else gt=true; }if (lt && gt) puts("Not Heap");else if (lt) puts("Min Heap");else puts("Max Heap");

    建立

    //動態插入//大根堆,插入一個上調一個for(int i=1;i<=n;i++){ cin>>h[i]; up(i);}//標準堆的初始化for(int i=n/2;i>=0;i--){ //從最后一個非葉子節點開始,down down(i);}

    基本操作

    //向下調整int h[N];void down(int u){ int t = u;//要和最小的孩子交換 if(t*2<size && h[u*2]<h[t]) t=u*2; if(t*2+1<size && h[u*2+1]<h[t]) t=u*2+1; if(u!=t){ //如果u需要被交換 swap(h[u],h[t]); down(t); }}//大根堆向上調整void up(int u){ while(u/2&&h[u/2]<h[u]){ //如果父節點存在且父節點小于該節點,交換,遞歸調整父節點 swap(h[u/2],h[u]); u/=2; }}

    插入

    //插入到最后一個位置,然后upheap[++size] = x;up(size);

    刪除

    // 1. 刪除最小值//把最后一個元素覆蓋到堆頂,刪除后然后downheap[1] = heap[size--];down(1);// 2. 刪除任意元素,修改也是一個道理heap[k] = heap[size--];if(k變小了){ up(k);}else { down(k);}//或者直接down(k);up(k); //只會執行里面的一個

    實際題目中的應用

    幾種實現方式:

  • 手寫堆

  • 優先隊列 — 其實如果有些題只是要用到堆這個數據結構用來排序優化找最值可以用set替代(例如dijkstra的堆優化)【優先隊列和set一個原理】(但是不支持修改任意元素,會產生冗余)

  • 優先隊列不支持修改內部元素和遍歷,還是用set吧。。。

  • 使用stl的make_heap(),可以用vector模擬heap方便訪問內部元素

    #include<algorithm> vector<int> heap; //建立 auto cmp = [](const int x,const int y){return x>y;}; //匿名函數,類型只能是auto make_heap(heap.begin(),heap.end(),cmp);//這里cmp可以用greater和less,greater是小根堆,less是大根堆; 默認大根堆 //當然對于特定題目條件最好還是自己寫cmp,比如有時候是<=,有時候是<???//插入 heap.push_back(data); //先用push_back插入,然后push_heap一下 push_heap(heap.begin(),heap.end(),cmp); //刪除 pop_heap(heap.begin(),heap.end(),cmp); //把堆頂丟到最后面,所以pop_heap之后再pop_back()一下即可 heap.pop_back();//排序,排序后就不是一個堆了 sort_heap(heap.begin(),heap.end(),cmp); //使用前要保證符合堆特性is_heap(); //判斷是否能是一個二叉堆 is_heap_until() //返回第一個破壞二叉堆結構元素的迭代器
  • 完整的模擬堆

    #include<string>#include<iostream>#include<algorithm>#define N 100010using namespace std;//這里由于要刪除和修改第k個插入的數,開ph和hp兩個數組int h[N],hsize,ph[N],hp[N]; //ph[k]表示第k個插入的數的下標,hp[k]堆里面的點是第幾個插入的void heap_swap(int a,int b){ //交換堆中兩個元素 swap(ph[hp[a]],ph[hp[b]]); //交換第k個插入的下標 swap(hp[a],hp[b]); //交換兩結點時第幾個交換的 swap(h[a],h[b]); //交換堆中兩個元素}//小根堆void down(int u){ int t=u; //找到左右孩子中最小的那一個,然后和父親交換,遞歸down孩子 if(u*2<=hsize&&h[u*2]<h[t]) t=u*2; if(u*2+1<=hsize&&h[u*2+1]<h[t]) t=u*2+1; if(u!=t){ heap_swap(u,t); down(t); }}void up(int u){ while(u/2&&h[u/2]>h[u]){ //如果父親比孩子大,交換,再看新父親是否需要up heap_swap(u/2,u); u/=2; }}int main(){ int n,m=0; scanf("%d",&n); getchar(); while(n--){ string cmd; cin>>cmd; if(cmd=="I"){ //插入 int x; cin>>x; hsize++; m++; //表示第幾個插入的 ph[m]=hsize; hp[hsize]=m; h[hsize]=x; up(hsize); }else if(cmd=="D"){ //刪除第k個 int k; cin>>k; k = ph[k]; heap_swap(k,hsize); hsize--; down(k),up(k); }else if(cmd=="C"){ //修改第k個 int k,x; cin>>k>>x; k=ph[k]; h[k]=x; down(k),up(k); }else if(cmd=="PM"){ //輸出最小 printf("%d\n",h[1]); }else{ //刪除最小 heap_swap(1,hsize); //由于存在下標映射,所有交換都改heap_swap hsize--; down(1); } } return 0;}

    對頂堆

    上面用一個小根堆維護,下面用大根堆,下面元素個數比上面多一個或一樣多;則中位數一定在下面的堆頂
    小根堆堆頂作為分界線
    如果兩堆個數相差>=1,把多的堆頂彈出加入另一堆頂
    (可以使用兩個set模擬)

    插入

    把x和下面堆的堆頂比較,大于插入上面的堆,x<=下面的堆頂,插入下面。
    **調整:**如果上面元素多,把上面的最小值pop插入到下面的堆;如果下面多則把下面的最大值插入到上面

    刪除

    和下面堆的最大值比較,小于就在下面刪除,否則在上面刪除

    應用

    • 求中位數

    具體實現

    multiset<int> up,down; //上面存大的,小根堆,下面存小的,大根堆,下面元素個數永遠比上面打一個或相同,中位數再下面的堆頂void adjust(){ while(up.size()>down.size()){ //pop上面最小的,插入下面 down.insert(*up.begin()); up.erase(up.begin()); } while(down.size()>up.size()+1){ //pop下面最大的,插入上面 auto it = down.end(); it--; up.insert(*it); down.erase(it); }}cin>>x;//插入if(up.empty()||x<*up.begin()) down.insert(x); //插入下面的情況:1.上面為空;2.小于上面最小值else up.insert(x);adjust();//刪除auto it = down.end();it--;if(x<*it) down.erase(down.find(x)); //小于下面最大的,在下面else up.erase(up.find(x)); //否則在上面adjust();//中位數就是下面的最大值auto it = down.end();it--;cout<<*it;

    前綴和

    前綴和用于快速求數組中一段區間內的和 【下標一定要從1開始
    如我們要求Dl+Dl+1+…Dr**(原數組中一段區間內的和)**
    則S0=0,S1=D1,S2=D1+D2,Si表示前i個數組元素的和,然后用Sr-(Sl-1)

    //求Sis[0]=0;for(int i=1;i<=n;i++){ s[i]=s[i-1]+a[i];}//求區間l到r內和cout<<s[r]-s[l-1];

    二維前綴和

    //初始化S[i][j]s[0][0]=0;for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) s[i][j] = s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];//求一段子區域和(x1,y1)->(x2,y2) 就類似概率論離散求分布函數一樣cout<<s[x2][y2]-s[x2][y1-1]-s[x1-1,y2]+s[x1-1][y1-1];

    對于環的處理,把它展開 如1->2->3->4->5, 5->1轉成1 2 3 4 5 1 2 3 4 5
    那么,1到3就可以走1->2->3也可以走3->4->5->1

    差分

    差分就是前綴和的逆,類似微分和積分
    O(n)時間內從差分數組B求一次前綴和到原數組A,用途,高效讓A內一段區間內的值+C

    int a[N],b[N]; //a原數組,b差分數組void insert(int l,int r,int c){ b[l]+=c,b[r+1]-=c;}for(int i=1;i<=n;i++) insert(i,i,a[i]); //初始化差分數組//若要累加insert(l,r,c);for(int i=1;i<=n;i++) b[i]+=b[i-1]; //求一下前綴和for(int i=1;i<=n;i++) printf("%d ",b[i]); //打印序列

    貪心

    區間貪心

    1. 選擇不相交區間(做法和區間選點一樣)

    描述
    n個開區間(ai,bi),選擇盡量多個區間,之間沒有公共點(例如在時間不沖突情況下選盡量多課)
    做法

  • 按照右端點從小到大排序,從第一個區間開始選
  • 把所有和上一個區間相交的區間排除在外
  • //例如 struct req {string start;string end;bool operator<(const req &t)const {return end < t.end; //結束時間早的排在前面} } list[N]; for (int i = 0; i < n; i++)cin >> list[i].start >> list[i].end;sort(list, list + n); //排序int res = 0;string prev;for (int i = 0; i < n; i++) {if (list[i].start >= prev) { //下一個開始時間晚于上一個結束時間則選擇,否則不選prev = list[i].end;res++;}}

    2. 區間選點(和上面的一毛一樣)

    描述
    n個閉區間[ai,bi],選擇盡量少的點,使每個區間內都至少一個點
    做法

  • 按區間右端點從小到大排(右端點相同時按左端點從大到小)
  • 從前往后依次枚舉每個區間,若當前區間已經包含點,跳過;否則,選當前區間右端點
  • //遍歷代碼如下int ed=-2e9; //將ed初始化為負無窮for(int i=0;i<n;i++){ if(arr[i].l>ed){ //該區間的左端點>上一個右端點 cnt++; ed=arr[i].r; }}

    3. 區間分組

    描述:給定 N 個閉區間 [ai,bi],將這些區間分成若干組,使得每組內部的區間兩兩之間(包括端點)沒有交集,并使得組數盡可能小。
    做法

  • 將所有區間按左端點從小到大排序
  • 從前往后處理每個區間,判斷能否將其放到某個現有的組(即:該區間左端點是否大于組內右端點最大值,大于即有交集,不能放)
  • 如果不存在這樣的組,開新組
  • 存在,放進去并更新 Max_r
  • //實現遍歷的時候使用一個小根堆維護組內區間右端點的最小值priority_queue<int,vector<int>,greater<int>> heap;for(int i=0;i<n;i++){ auto s = list[i]; //當前區間 if(heap.empty()||heap.top()>=s.left) heap.push(s.right); //當前左端點小于右端點的最小值,新開一個組 else { int t=heap.top(); heap.pop(); heap.push(s.right); } cout<<heap.size()<<endl; //組的個數}

    4. 區間覆蓋

    描述:N個閉區間[ai,bi]以及一個線段區間[s,t],請你選擇盡量少的區間,將指定線段區間完全覆蓋。(一個目標區間,n個備選區間,從備選的中選最少的覆蓋目標區間)
    做法

  • 將所有區間按左端點從小到大排序
  • 從前往后枚舉每個區間,在所有能覆蓋開始位置start的區間中,選右端點最大的區間。然后將start更新成右端點的最大值
  • bool find = false; //找到方案bool變量for(int i=0;i<n;i++){ int j=i,r=-2e9; //r是能覆蓋開始位置start的的區間中右端點最大值 while(j<n&&list[j].left<=start){ //遍歷所有左端點在目標區間內右端點的最大值是多少 r=max(r,list[j].right); j++; } if(r<st){ res = -1; //無解 break; } res++; if(r>=ed){ find = true; break; } st=r; i=j-1;}if(!find) ...

    H指數

    排序后看最大的i個數是不是滿足都大于i,即排序后看倒數第i個數是否大于i

    置換群

    轉換成圖論
    i在p[i](有序序列)的位置上,就讓i到p[i]連一條邊
    此時連成的圖的每個點出度入度都是1,它必然是一堆環
    如果要交換排序,目標就是把n個環變成n個自環
    而操作: 交換兩個數 <=> {
    ? 一個環變成兩個環(環內交換):{
    - 和0【可以操作的點】的下一個點交換,下一個點會變自環 - 和非next點交換會變兩個環,到時候還是要合并回來(redundancy)
    ? }
    ? 兩個環變一個環(環外交換
    }

    //例題:swap(0,i)操作進行排序 #include... using namespace std; const int N = 100010; int n,p[N]; int main(){cin>>n;for(int i=0;i<n;i++){int id;cin>>id;p[id] = i; //記錄位置}int res = 0;for(int i=1;i<n;i++) //掃描所有不是自環的點{while(p[0]!=0) swap(p[0],p[p[0]]),res++; //把0在的環變自環,單0不在原本位置時,和0的下一個數交換while(i<n&&p[i]==i) i++;if(i<n) swap(p[0],p[i]),res++; //此時i必然在另一個環里面}printf("%d\n",res);return 0; }

    哈夫曼樹

    可以使用優先隊列模擬小根堆

    #include<iostream>#include<queue>#include<vector>using namespace std;int n,x;int main(){ cin>>n; priority_queue<int,vector<int>,greater<int>> heap; for(int i=0;i<n;i++) cin>>x,heap.push(x); int res=0; while(heap.size()>1){ int a = heap.top();heap.pop(); int b = heap.top();heap.pop(); res+=a+b; heap.push(a+b); } cout<<res<<endl; return 0;}

    排序不等式(打水問題)

    總時間 = t1×n-1+t2×(n-2)+t3×(n-3)… [計算總時間的公式]

    描述:給n個人打水時間,安排打水順序使得等待時間最小

    做法

    按時間排序,然后總時間按公式算(注意可能爆出int,最好用long long)

    sort(t,t+n);for(itn i=0;i<n;i++) res+=t[i]*(n-i-1); //也可以for(int i=1;i<=n;i++) res+=t[i-1]*(n-i)printf("%lld\n",res);

    絕對值不等式

    描述:在一條數軸上有N個點表示商家an,an中選一個點x到其他商家距離之和最小
    即:max{|a1-x|+|a2-x|+|a3-x|+…}
    做法:
    對所有an排序,n是奇數,x選中位數;n是偶數,中間兩個數都可以,總和就是每個點到中位數的點的距離之和

    sort(a,a+n);int res=0;for(int i=0;i<n;i++) res+=abs(a[i]-a[n/2]);

    推公式

    比如耍雜技的牛,排序后算就可以了

    動態規劃 dp

    概念

    具有最優子結構,核心是找到狀態轉移方程邊界條件

    滾動數組

    若更新狀態只依賴于與之前的狀態,可以使用滾動數組,降低空間復雜度

    關于時間

    還是看看算法筆記吧。。。

  • 全部轉換為統一單位
  • 使用string
  • scanf(%d:%d:%d),轉換成總秒數之類的…
  • 輸出持續時間可以通過總秒數轉換而來,例如printf("%02d:%02d:%02d",sum/3600,sum%3600/60,sum%60);
  • 數學

    最大公約數(約數:因數)gcd

    //輾轉相除法(歐幾里得算法)int gcd(int a,int b){ return b?gcd(b,a%b):a; }

    最小公倍數 lcm

    在gcd的基礎上,把ab/d,防止溢出就是a/d*b

    int lcm(int a,int b){ int d = gcd(a,b); return a/d*b;}

    拓展歐幾里得

    求x、y,使得ax+by=gcd(a,b)

    int exgcd(int a,int b,int &x,int &y){ if(!b){ x=1,y=0; return a; } int d = exgcd(b,a&b,y,x); y -= (a/b)*x; return d;}

    分數

  • down必須為非負數,分數為負則另up為負
  • 分數為0則令up為0,down為1
  • 分子和分母需要時刻保持最簡
  • typedef long long int LL; //防止溢出LL gcd(LL a,LL b){ return b?gcd(b,a%b):a;}struct Fraction{ LL up,down; void reduction(){ if(down<0){ up=-up; down=-down; } if(up==0) down=1; else{ int d = gcd(abs(up),abs(down)); //這里記得加絕對值 up/=d; down/=d; } } Fraction operator+(const Fraction &FR){ Fraction r; r.down = down*FR.down; r.up = up*FR.down+FR.up*down; r.reduction(); return r; } Fraction operator-(const Fraction &FR){ Fraction r; r.down = down*FR.down; r.up = up*FR.down-FR.up*down; r.reduction(); return r; } Fraction operator*(const Fraction &FR){ Fraction r; r.down = down*FR.down; r.up = up*FR.up; r.reduction(); return r; } Fraction operator/(const Fraction &FR){ Fraction r; r.down = down*FR.up; r.up = up*FR.down; r.reduction(); return r; }};//輸出if(r.down==1) printf("%lld\n",r.up); //integerelse if(abs(r.up)>abs(r.down)) printf("%d %d/%d",r.up/r.down,abs(r.up)%r.down,r.down);//fakeelse printf("%d/%d",r.up,r.down); //real

    質數

    判斷質數(試除法)

    約數定理:一個數的所有約數(因數)是成對出現:如果d是n的約數,則n/d也是n的約數,故只要枚舉d<=n/d的約數 --> d<=根號n(試除法)

    bool isPrime(int n){ if(n==1) return false; for(int i=2;i*i<=n;i++) //這里優化寫法是寫成 for(int i=2;i<=n/i;i++) 防止i*i溢出 if(n%i==0) return false; return true;}

    素數篩

    線性篩

    bool st[N]; //標記一個數是不是素數int primes[N]; //存儲素數int cnt;void get_primes(int x){ for(int i=2;i<=x;i++){ if(!st[i]) primes[cnt++]=i;//如果是素數,加入素數表中 //篩選 for(int j=0;primes[j]<=x/i;j++){ //從所有素數中篩選 st[primes[j]*i] = true; if(i%primes[j]==0) break;//primes[j]一定是i的最小質因子 } }}

    分解質因子

    從小到大枚舉所有約數,如果可以被除,就除干凈

    //分解質因子 x是要分解的數,k是質因子相乘個數void divide(int x){ for(int i=2;i<=x/i;i++){ if(x%i==0){ int s=0; while(x%i==0) x/=i,s++; cout<<i<<' '<<s<<endl;//底數和指數 } } if(x>1) cout<<x<<' '<<1<<endl; //唯一一個大于根號x的質因數 cout<<endl;}

    計算n!的質因子個數

    int cal(int n,int p) //計算n!有多少個質因子p{ if(n<p) return 0; return n/p+cal(n/p,p);}

    約數

    求所有約數(試除法)

    vector<int> get_divisor(int x){ vector<int> res; for(int i=1;i<=x/i;i++) //這里和素數不一樣從1開始 if(x%i==0){ res.push_back(i); if(i!=x/i) res.push_back(x/i); } sort(res.begin(),res.end()); return res;}

    歐拉函數

    求歐拉函數

    /phi(N) = 1~N中與N互質的數的個數

    int phi(int x){ int res=x; for(int i=2;i<=x/i;i++){ if(x%i==0){ res=res/i*(i-1); //res/=質數*(質數-1) while(x%i==0) x/=i; } } if(x>1) res=res/x*(x-1); return res;}

    線性篩歐拉函數

    int primes[N],cnt; //存儲所有素數int euler[N]; //存儲每個數的歐拉函數bool st[N]; //標記x是否被篩掉void get_eulers(int x){ euler[1]=1; for(int i=2;i<=x;i++){ if(!st[i]){ primes[cnt++]=i; euler[i]=i-1; } for(int j=0;primes[j]<x/i;j++){ int t = primes[j]*i; st[t]=true; if(i%primes[j]==0){ euler[t]=euler[i]*primes[j]; break; } euler[t]=euler[i]*(primes[j]-1); } }}

    快速冪

    LL qmi(int a,int b,int p){ LL res=1%p; while(b){ if(b&1) res=res*a%p; a=a*(LL)a%p; b>>=1; } return res;}

    組合數

    計算Cmn(m在下面) [需要根據數據范圍選方法]

    //線性,可能乘法溢出typedef long long LLLL C(LL n,LL m){ LL ans = 1; for(LL i=1;i<=m;i++) ans=ans*(n-m+i)/i; return ans;}//遞歸LL C(LL n,LL m){ if(m==0||n==m) return 1; return C(n-1,m)+C(n-1,m-1);}//遞推LL res[N][N];LL C(LL n,LL m){ if(m==0||m==n) return 1; if(res[n][m]!=0) return res[n][m]; return res[n][m]=C(n-1,m)+C[n-1][m-1];}//遞推打表(m,n<=2000)int c[N][N];void init(){ for(int i=0;i<N;i++) for(int j=0;j<=i;j++) if(!j) c[i][j]=1; else c[i][j]=(c[i-1][j]+c[i-1][j-1]);}

    關于逆元

    計算Cmn%p

    //逆元、費馬小定理(m,m<=100000)int fact[N],infact[N]; //階乘%p,逆元%pfact[0]=infact[0]=1;int qmi(int a,int k,it p); //快速冪{ int res = 1; while(k){ if(k&1) res = (LL)res*a%p; k>>=1; } return res;}for(int i=1;i<N;i++){ fact[i]=(LL)fact[i-1]*i%mod; infact[i]=(LL)infact[i-1]*qmi(i,p-2,p)%mod;}Cab%mod(LL)fact[a]*infact[b]%mod*infact[a-b]%mod;//Lucas 要求p是素數int Lucas(int n,int m){ if(m==0) return 1; return C(n%p,m%p)*Lucas(n/p,m/p)%p;}

    卡特蘭數

    int Cat(n){ return C(2*n,n)/(n+1);}

    TIPS

    • 要找到小的可以使用swap,stl也可以直接swap

    總結

    以上是生活随笔為你收集整理的算法笔记(自用)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。