<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      算法復雜度

      時間復雜度: 常對冪指階

      時間復雜度
      時間復雜度指輸入數據大小為 N 時,算法運行所需花費的時間。統計的是算法的「計算操作數量」,而不是「運行的絕對時間」。
      時間復雜度具有「最差」、「平均」、「最佳」三種情況,分別使用 O ,Θ ,Ω 三種符號表
      常見種類: O(1)<O(logN)<O(N)<O(N*logN)<O(N2)<O(2n)<O(N!)

      N2 :如冒泡排序
      指數階常出現于遞歸
      階乘階對應數學上常見的 “全排列”
      對數階與指數階相反,指數階為 “每輪分裂出兩倍的情況” ,而對數階是 “每輪排除一半的情況” 。對數階常出現于「二分法」、「分治」等算法中,體現著 “一分為二” 或 “一分為多” 的算法思想。
      線性對數階(N*logN)常出現于排序算法,例如「快速排序」、「歸并排序」、「堆排序」等

      空間復雜度

      通常情況下,空間復雜度指在輸入數據大小為 N 時,算法運行所使用的「暫存空間」+「輸出空間」的總體大小。
      而根據不同來源,算法使用的內存空間分為三類:
      指令空間:編譯后,程序指令所使用的內存空間。
      數據空間:算法中的各項變量使用的空間,包括:聲明的常量、變量、動態數組、動態對象等使用的內存空間。
      棧幀空間:程序調用函數是基于棧實現的,函數在調用期間,占用常量大小的棧幀空間,直至返回后釋放。棧幀空間的累計常出現于遞歸調用
      常見種類:O(1)<O(logN)<O(N)<O(N2)<O(2N)

      acwing模板

      基礎算法

      排序

      快速排序

      快速排序是不穩定的
      關鍵:在于如何劃分為兩個序列
      時間復雜度:O(n logn)
      img

      const int N = 1e6 + 10;
      int q[N];
      int n;
      void quick_sort(int q[], int l, int r){//l是左邊界,r是右邊界
          if(l >= r) return;//當l和r相遇時,交換l和r位置
      
          int x = q[l + r >> 1];//定義一個中間值,用于比較,把q[]分成大于和小于兩部分,更新了數據,不能用最左邊和最右邊了
          int i = l-1, j = r+1;
          while(i < j){
              do i++; while(q[i] < x);
              do j--; while(q[j] > x);
              if( i < j ) swap(q[i],q[j]);//如果兩個數滿足在q[]的分別在另一邊,交換位置
          }
          quick_sort(q,l, j);
          quick_sort(q, j + 1, r);//兩側迭代,直到有序
      }
      

      歸并排序

      歸并排序是穩定的
      關鍵:在于如何合并連個有序序列,雙路歸并,合二為一
      時間復雜度:O(n logn)
      img

      
      const int N = 1e6 + 10;
      int n;
      int a[N],tmp[N];
      
      void merge_sort(int q[], int l, int r){
          if(l >= r) return;
          //確定分界點
          int mid = l + r  >>1;
          //遞歸分成左右兩段
          merge_sort(q,l,mid);
          merge_sort(q, mid+1, r);
          int k = 0, i = l, j = mid + 1;
          while(i <= mid&&j <= r){//判斷是否結束遍歷
              if(q[i] <= q[j]) tmp[k++] = q[i++];
              else tmp[k++] = q[j++];
          }
          while(i <= mid) tmp[k++] = q[i++];
          while(j <= r) tmp[k++] = q[j++];
          
          //把tmp臨時數組再賦值回去
          for(int i = l, k = 0; i <= r; i++){
              q[i] = tmp[k++];
          }
       }
      
      

      快速排序和歸并排序區別

      1.速排序是原地排序,原地排序指的是空間復雜度為O(1);
      2.歸并排序不是原地排序,因為兩個有序數組的合并需要額外的空間協助才能合并;
      3.快速排序是不穩定的,時間復雜度在O(nlogn)~O(n2)之間 。歸并排序是穩定的,時間復雜度是O(nlogn)。
      4.快速排序是二叉樹模型
      5.歸并排序是到二叉樹模型

      二分

      有序元素(有某種性質)+查找 == 二分

      整數二分

      二分的本質不是單調性,是二段性,將區間一分為二,一半滿足,一半不滿足
      img
      img
      找分界點位置,分為兩種,
      第一種,找紅色邊界點,l = mid,而且mid = l+r+1 >>1

      第二種,找綠色邊界點,r = mid,mid = l + r >> 1

      int l = 0, r = n-1;
      bool check(int x) {/* ... */} // 檢查x是否滿足某種性質
      
      // 區間[l, r]被劃分成[l, mid]和[mid + 1, r]時使用:
      int bsearch_1(int l, int r)
      {
          while (l < r)
          {
              int mid = l + r >> 1;
              if (check(mid)) r = mid;    // check()判斷mid是否滿足性質
              else l = mid + 1;
          }
          return l;
      }
      // 區間[l, r]被劃分成[l, mid - 1]和[mid, r]時使用:
      int bsearch_2(int l, int r)
      {
          while (l < r)
          {
              int mid = l + r + 1 >> 1;
              if (check(mid)) l = mid;
              else r = mid - 1;
          }
          return l;
      }
      

      有點亂,看到還有一種方法,試試看
      模板
      img
      img

      const int N = 1e6 + 10;
      int q[N];
      bool check(int x){
          //要求數據的補集
          if() return true;
          return false;
      }
      int l = -1, r = n ;
      while(l + 1 != r){
          int  mid = l + r >> 1;
          if(check(q[mid])){
              l = m;
          }
          else r = m;
      }
      
      

      這個方法,更容易出錯,返回l,r也有區別

      浮點數二分

      這個比較簡單,一般用于開方

      int l = -100, r = 100;
      while(r - l > 1e-6){//根據題目要求,一般情況下比題目保留位數大兩位
          int mid = ( l + r ) / 2;
          if(mid * mid >= x) r = m;//x為帶求開方數
          else l = m; 
      }
      
      

      高精度

      高精度一般考四種
      存儲方式用數組,倒序存,從個位存到高位

      a + b 位數一般106

      img

      #include <iostream>
      #include <cstring>
      #include <algorithm>
      #include <vector>
      using namespace std;
      //加法
      vector<int>add(vector<int>&A, vector<int> &B){//這里用引用避免了復制,節省了時間
          vector<int>c;
          int t = 0;//t表示進位
          for(int i = 0; i < A.size()||i < B.size(); i++){
              if(i < A.size()) t += A[i];
              if(i < B.size()) t += B[i];
              c.push_back(t%10);
              t /= 10;
          }
          if(t != 0)
          c.push_back(t);
          return c;
      }
      int main()
      {
          string a,b;
          vector<int>A,B;
          cin>>a>>b;
          //倒序存放到數組的過程
          for(int i = a.size() - 1; i >= 0; i --) A.push_back(a[i] - '0');
          for(int i = b.size() - 1; i >= 0; i --) B.push_back(b[i] - '0');
          //加法
          auto c = add(A,B);
          //倒序取出
          for(int i = c.size() - 1 ; i >= 0; i --){
              printf("%d",c[i]);
          }
          return 0;
      }
      

      a - b 位數一般106

      t表示借位
      img

      #include <iostream>
      #include <string>
      #include <vector>
      using namespace std;
      const int N = 1e5 + 10;
      //判斷a >= b
      bool cmp(vector<int> &a, vector<int> &b){
          if(a.size() != b.size()) return a.size() > b.size();
          
              for(int i = a.size() -1; i >= 0; i--){
                      //找到一位不同數字
                      if(a[i] != b[i])
                          return a[i] > b[i];
              }
          
          return true;
      }
      //c = a - b
      vector<int> sub(vector<int> &a, vector<int> &b){
          vector<int> c;
          for(int i = 0, t = 0; i < a.size(); i++){
              //t表示借位
              t = a[i] - t;
              //判斷b還有數字
              if(i < b.size()) t -= b[i];
              //原本要分為兩種情況,t > 0 和 t < 0, 這樣處理直接不需要分
              c.push_back((t + 10) % 10);
              if(t < 0) t = 1;//表明要借位
              else t = 0;
          } 
          while (c.size() > 1 && c.back() == 0) c.pop_back();
          return c;
      }
      int main()
      {
          string n,m;
          cin>>n>>m;
          vector<int> a, b;
          for(int i = n.size() - 1; i >= 0; i--) a.push_back(n[i] - '0');
          for(int j = m.size() - 1; j >= 0; j--) b.push_back(m[j] - '0');
      
          vector<int>c; // 存結果
          //如果a>b
          if(cmp(a,b)) {
              c = sub(a,b);
          }
      
          else c = sub(b, a), cout<<"-";
          //輸出
          for(int i = c.size() - 1; i >= 0; i--){
              cout<<c[i];
          }
          cout<<endl;
          return 0;
      } 
      
      
      

      a * b 大整數乘以一個小整數len(a) ≤ 106 且b ≤ 106

      把小的數字當做一個整體

      #include <iostream>
      #include <vector>
      using namespace std;
      // c =  a * b
      vector<int> mul(vector<int> &a, int b)
      {
          vector<int> c;
          int t = 0;                               // 進位
          for (int i = 0; i < a.size() || t; i++) // 當i沒有處理完,或者t不為0
          {
              if (i < a.size())
                  t += a[i] * b;
              c.push_back(t % 10);
              t /= 10;
          }
          while(c.size() > 1&&c.back() == 0) c.pop_back();
          return c;
      }
      int main()
      {
          string a;
          int b;
          cin >> a >> b;
          vector<int> c;
          for (int i = a.size() - 1; i >= 0; i--)
          {
              c.push_back(a[i] - '0');
          }
          auto s = mul(c, b);
      
          for (int i = s.size() - 1; i >= 0; i--)
          {
              cout << s[i];
          }
          return 0;
      }
      
      

      a / b 求商和余數 大整數除以一個小整數len(a) ≤ 106 且b ≤ 106

      注意倒著存,正著算
      img

      #include <iostream>
      #include <vector>
      #include <algorithm>
      using namespace std;
      // a/b,商是c,余數是r
      vector<int> div(vector<int> &a, int b, int &r)
      {
          vector<int> c;
          r = 0;
          // 注意這里是正著的算的,但是a是倒著存的,所以如下
          for (int i = a.size() - 1; i >= 0; i--)
          {
              r = r * 10 + a[i];
              c.push_back(r / b);
              r %= b;
          }
          //翻轉過來,正著除,倒著存,翻轉過來
          reverse(c.begin(), c.end());
          // 去掉前導0
          while (c.size() > 1 && c.back() == 0)
              c.pop_back();
          return c;
      }
      int main()
      {
          string a;
          int b;
          cin >> a >> b;
          vector<int> c;
          for (int i = a.size() - 1; i >= 0; i--)
          {
              c.push_back(a[i] - '0');
          }
          int r;
          auto s = div(c, b, r);
      
          for (int i = s.size() - 1; i >= 0; i--)
          {
              cout << s[i];
          }
          cout << endl<< r;
          return 0;
      }
      

      前綴和

      一維前綴和

      前綴和一定要下標從1開始,快速求出數組中一段數的和,sum[0] = 0;

      const int N = 100001;
      int sum[N],a[N];
      for(int i = 1; i <= n; i++){
          sum[i] = sum[i-1] + a[i];
      }
      int ans = sum[r] - sum[l-1];
      

      詢問是O(1),預處理過程中是O(n)

      對于二維矩陣的前綴和

      const int N = 1010;
      int sum[N][N],a[N][N];
      for(int i = 1; i <= n; i++){
          for(int j = 1; j <= m; j++){
              cin>>a[i][j];
              sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + a[i][j];
          }
      }
      //(x2,y2) - (x1,y1);
      int ans = sum[x2][y2] - sum[x2][y1-1] - sum[x1-1][y2] + sum[x1-1][y1-1];
      

      差分

      一維差分

      構造一個b數組使得a是b的前n項和

      #include<iostream>
      using namespace std;
      const int N = 100010;
      int n,m;
      int A[N],B[N];//b數組是a數組的差分,a數組是b數組的前綴和
      void insert(int l, int r, int c){
          B[l] += c;
          B[r+1] -= c;
      }
      int main(){
          scanf("%d%d", &n, &m);
          for(int i = 1; i <= n;i++){//注意從一開始
              scanf("%d",&A[i]);
          }
          for(int i = 1; i <= n; i++) insert(i,i, A[i]);
          while(m--){
              int l,r,c;
              scanf("%d%d%d",&l,&r,&c);
              insert(l,r,c);
          }
          for(int i = 1; i<= n; i++){
              B[i] += B[i-1];
              printf("%d ",B[i]);
          }
          return 0;
      }
      

      二維差分

      img

      #include <iostream>
      #include <cstring>
      #include <algorithm>
      
      using namespace std;
      const int N = 1010;
      int A[N][N],B[N][N];
      void insert(int x1, int y1, int x2, int y2, int c){
          B[x1][y1] += c;
          B[x2 + 1][y1] -= c;
          B[x1][y2+1] -= c;
          B[x2 + 1][ y2 + 1] += c;
          
      }
      int main(){
          int n,m,q;
          cin>>n>>m>>q;
          for (int i = 1; i <= n; i ++ ){
              for (int j = 1; j <= m; j ++ ){
                  cin>>A[i][j];
                  insert(i,j,i,j,A[i][j]);
              }
          }
          while(q--){
              int x1,y1,x2,y2,c;
              cin>>x1>>y1>>x2>>y2>>c;
              insert(x1,y1,x2,y2,c);
          }
          for(int i= 1; i <= n; i++){
              for(int j = 1; j <= m; j++){
                      B[i][j] +=  B[i][j-1]+ B[i-1][j] - B[i-1][j-1];
                      printf("%d ",B[i][j]);
              }
          
              cout<<endl;
          }
          
      }
      

      雙指針算法

      找到某種單調性
      可以把O(n2) 的算法優化成O(n)
      img
      雙指針算法將暴力的優化一下

      位運算

      操作符 數字a 數字b 結果
      & 0101 0001 0001
      0101 0001 0101
      ~ 0101 1010
      ^ 0101 0001 0100
      >>(右移) /=2
      <<(左移) *=2

      n的二進制表示中,第k位是幾
      先把k右移到最后一位 n >> k (n>>1 == n/=2)
      在進行與運算 n & 1;
      組合起來就是 n >> k & 1;

      lowbit(x)是返回x的最后一位1是多少,可以用來統計x中1的個數
      img
      實現方式
      img

      #include <iostream>
      #include <cstring>
      #include <algorithm>
      using namespace std;
      int lowbit(int x){
          return x & -x;
      }
      int main(){
          int n;
          cin>>n;
          while(n--){
              int x;
              cin>>x;
              int res = 0;
              while(x) {
                  x -= lowbit(x);//減去x中最后一位1
                  res++;
              }
              cout<<res <<' ';
          }
          return 0;
      }
      

      原碼:原來的 x
      反碼:每一位取反 ~x
      補碼:取反在加一 ~x+1

      離散化

      這里特指有序、飽序的數字的離散化
      img
      模板

      vector<int> alls; // 存儲所有待離散化的值
      sort(alls.begin(), alls.end()); // 將所有值排序
      alls.erase(unique(alls.begin(), alls.end()), alls.end());   // 去掉重復元素
      
      // 二分求出x對應的離散化的值
      int find(int x) // 找到第一個大于等于x的位置
      {
          int l = 0, r = alls.size() - 1;
          while (l < r)
          {
              int mid = l + r >> 1;
              if (alls[mid] >= x) r = mid;
              else l = mid + 1;
          }
          return r + 1; // 映射到1, 2, ...n
      }
      

      unique實現

      img

      //a[j] 中存儲的就是不重復元素
      vector<int>::iterator unique(vector<int> &a){
          int j = 0; 
          for(int i = 0; i < a.size(); i++){
              if(!a || a[i]!=a[i-1]){
                  a[j++] = a[i];
              }
          }
          return a.begin() + j;
      }
      
      

      這是典例,區間和

      #include <iostream>
      #include <vector>
      #include <cstring>
      #include <algorithm>
      using namespace std;
      const int N = 300010;
      typedef pair<int, int> PII;
      vector<int> alls;//存儲要離散化的位置
      vector<PII> add,query;//插入操作,查詢
      int a[N],s[N];//a是輸入的數,s是前綴和
      //求x離散化后的結果,這里用到二分
      int find(int x){
          int l = 0; 
          int r = alls.size() - 1;
          while(l < r){
              int mid = l + r >> 1;
              if(alls[mid] >= x) r = mid;//找大于等于x的最小值
              else l = mid + 1;
          }
          return r + 1;//方便進行前綴和
      }
      int main(){
          int n,m;
          cin >> n >> m;
          for (int i = 0; i < n; i++){
              int x,c;
              cin>>x>>c;
              //在x的位置插入c
              add.push_back({x,c});
              
              alls.push_back(x);
          }
          for (int j = 0; j < m; j++){
              int l,r;
              cin>>l>>r;
              query.push_back({l,r});
              
              alls.push_back(l);
              alls.push_back(r);
          }
          //去重alls
          sort (alls.begin(),alls.end());
          alls.erase(unique(alls.begin(),alls.end()), alls.end());
          //處理插入
          for(auto item : add){
              int x = find(item.first);
              a[x] += item.second;
          }
          //預處理前綴和
          for(int i = 0; i <= alls.size(); i++){
              s[i] = s[i-1] + a[i];
          }
          for(auto item : query){
              int l = find(item.first);
              int r = find(item.second);
              cout<<s[r] - s[l -1]<<endl;
          }
          return 0;
      }
      

      區間合并

      img
      按照左端點進行排序

      void merge(vector<PII> &segs)
      {
          vector<PII> res;
      
          sort(segs.begin(), segs.end());
      
          int st = -2e9, ed = -2e9;
          for (auto seg : segs)
          //沒有任何關系
              if (ed < seg.first)
              {
                  if (st != -2e9) res.push_back({st, ed});
                  st = seg.first, ed = seg.second;
              }
              //有關系,更新ed
              else ed = max(ed, seg.second);
          //把最后一個加入到區間里
          if (st != -2e9) res.push_back({st, ed});
      
          segs = res;
      }
      
      

      數據結構

      鏈表與鄰接表:樹與圖的存儲

      數組模擬單鏈表

      主要是避免反復new結點,造成緩慢
      用于存儲樹和圖

      //head 表示頭指針
      //e[i] 表示下標為i的結點的值
      //next[i] 表示i的下一個結點
      //idx 表示當前指導那個位置
      int e[N],next[N],idx,head;
      //初始化
      void init(){
          head = -1;
          idx = 0;
      }
      //插入到頭結點
      void add_to_head(int x){
          e[idx] = x;
          next[idx] = head;
          head = idx;
          idx ++; 
      }
      //把x插入到下標為k的后面
      void add(int k, int x){
          e[idx] = x;
          ne[idx] = ne[k];
          ne[k] = idx;
          idx++;
      }
      //單鏈表刪除k結點下一個元素
      void remove(int k){
          ne[k] = ne[ne[k]];
      }
      
      //雙向鏈表
      int e[N],l[N],r[N],idx; //l[N]代表左側,r[N]代表右側
      
      void init(){
          idx = 2;
          //兩個邊界
          r[0] = 1;
          l[1] = 0;
      }
      
      //在k右邊插入x
      void add(int k, int x){
          e[idx] = x;
          l[idx] = k;
          r[idx] = r[k];
          l[r[k]] = idx;
          r[k] = idx;
          idx++;
      }
      //刪除第k個點
      void remove(int k){
          l[r[k]] = l[k];
          r[l[k]] = r[k];
      }
      
      

      鄰接表是多個單列表

      詳見第三章

      棧與隊列:單調隊列、單調棧

      // tt表示棧頂
      int stk[N], tt = 0;
      
      // 向棧頂插入一個數
      stk[ ++ tt] = x;
      
      // 從棧頂彈出一個數
      tt -- ;
      
      // 棧頂的值
      stk[tt];
      
      // 判斷棧是否為空,如果 tt > 0,則表示不為空
      if (tt > 0)
      {
      
      }
      

      單調棧

      找某個數最左邊或者右邊距離他最近的大于或者小于他的數

      常見模型:找出每個數左邊離它最近的比它大/小的數
      int tt = 0;
      for (int i = 1; i <= n; i ++ )
      {
          while (tt && check(stk[tt], i)) tt -- ;
          stk[ ++ tt] = i;
      }
      

      時間復雜度O(n)

      普通隊列

      // hh 表示隊頭,tt表示隊尾
      int q[N], hh = 0, tt = -1;
      
      // 向隊尾插入一個數
      q[ ++ tt] = x;
      
      // 從隊頭彈出一個數
      hh ++ ;
      
      // 隊頭的值
      q[hh];
      
      // 判斷隊列是否為空,如果 hh <= tt,則表示不為空
      if (hh <= tt)
      {
      
      }
      
      

      循環隊列

      // hh 表示隊頭,tt表示隊尾的后一個位置
      int q[N], hh = 0, tt = 0;
      
      // 向隊尾插入一個數
      q[tt ++ ] = x;
      if (tt == N) tt = 0;
      
      // 從隊頭彈出一個數
      hh ++ ;
      if (hh == N) hh = 0;
      
      // 隊頭的值
      q[hh];
      
      // 判斷隊列是否為空,如果hh != tt,則表示不為空
      if (hh != tt)
      {
      
      }
      

      單調隊列,滑動窗口

      //常見模型:找出滑動窗口中的最大值/最小值
      int hh = 0, tt = -1;
      for (int i = 0; i < n; i ++ )
      {
          while (hh <= tt && check_out(q[hh])) hh ++ ;  // 判斷隊頭是否滑出窗口
          while (hh <= tt && check(q[tt], i)) tt -- ;
          q[ ++ tt] = i;
      }
      

      原理差不多,等二刷

      #include<iostream>
      using namespace std;
      const int N = 1000001;
      int a[N], q[N];//q[N]存儲的是滑動窗口中的下標
      int main(){
          int n,k;
          scanf("%d%d", &n,&k);
          for(int i = 0; i < n; i++){
              scanf("%d",&a[i]);
          }
          int hh = 0, tt = -1;
          for(int i = 0; i < n; i++){
              //判斷隊頭元素是否已經滑出窗口
              if (hh <= tt && q[hh] < i - k + 1) hh++;
              while(hh <= tt && a[q[tt]] >= a[i]) tt--;
              q[++tt] = i;
              if(i >= k -1) printf("%d ",a[q[hh]]);
          }
          puts("");
          
          hh = 0, tt = -1;
          for(int i = 0; i < n; i++){
              if(hh <= tt && q[hh] < i - k + 1) hh++;
              while(hh <= tt && a[q[tt]] <= a[i]) tt--;
              q[++tt] = i;
              if(i >= k - 1) printf("%d ", a[q[hh]]);
          }
          puts("");
          return 0;
      }
      
      

      kmp

      // s[]是長文本,p[]是模式串,n是s的長度,m是p的長度
      求模式串的Next數組:
      for (int i = 2, j = 0; i <= m; i ++ )
      {
          while (j && p[i] != p[j + 1]) j = ne[j];
          if (p[i] == p[j + 1]) j ++ ;
          ne[i] = j;
      }
      
      // 匹配
      for (int i = 1, j = 0; i <= n; i ++ )
      {
          while (j && s[i] != p[j + 1]) j = ne[j];
          if (s[i] == p[j + 1]) j ++ ;
          if (j == m)
          {
              j = ne[j];
              // 匹配成功后的邏輯
          }
      }
      
      

      現在還不太懂代碼,但是原理懂了

      #include <iostream>
      #include <cstring>
      #include <algorithm>
      
      using namespace std;
      
      const int N = 100010, M = 1000010;
      int n,m;
      char p[N], s[M];
      int nex[N];
      int main(){
          cin>>n>>p + 1>>m >> s+1;
          for(int i = 2, j = 0; i <= n; i++){
              while(j && p[i] != p[j + 1]) j = nex[j];
              if(p[i] == p[j + 1]) j++;
              nex[i] = j;
          }
          for(int i = 1, j = 0; i <= m; i++){
              while(j && s[i] != p[j + 1]) j = nex[j];
              if(s[i] == p[j + 1]) j++;
              if(j == n){
                  printf("%d ", i- n);
                  j = nex[j];
              }
          }
          return 0;
      }
      
      

      Trie

      高效的存儲和查找字符串集合的數據結構
      img
      將所有字符串集合的結尾進行標記

      int son[N][26], cnt[N], idx;
      // 0號點既是根節點,又是空節點
      // son[][]存儲樹中每個節點的子節點
      // cnt[]存儲以每個節點結尾的單詞數量
      // idx 存儲當前用到那個下標
      // 插入一個字符串
      void insert(char *str)
      {
          int p = 0;//從根節點開始遍歷
          for (int i = 0; str[i]; i ++ )
          {
              int u = str[i] - 'a';//將a-z映射成0-25
              if (!son[p][u]) son[p][u] = ++ idx;//如果不存在,就創建出來
              p = son[p][u];//結束時對應的結尾位置為p
          }
          cnt[p] ++ ;
      }
      
      // 查詢字符串出現的次數
      int query(char *str)
      {
          int p = 0;
          for (int i = 0; str[i]; i ++ )
          {
              int u = str[i] - 'a';
              if (!son[p][u]) return 0;
              p = son[p][u];
          }
          return cnt[p];
      }
      
      

      并查集

      兩個操作:(近乎O(1))

      1.將兩個集合合并
      2.詢問兩個元素是否在一個集合中
      3.用樹的形式維護一個集合,根節點是他的代表元素,根節點的編號就是當前集合的編號,每個節點存儲他的父節點,p[x]表示x的父節點
      4.判斷樹根p[x] = x
      5.如何求x的集合編號:while(p[x] != x) x = p[x]
      6.如何合并兩個集合:將一個集合當成另外一個集合的子節點p[x]是x的集合編號,p[y]是y的集合編號,p[x] = y
      7.優化:
      路徑壓縮一旦找到根節點,就將該路徑上的所有點都指向根節點,基本就可以看成O(1)
      按秩合并:先留下,后面在補

      (1)樸素并查集:
      
          int p[N]; //存儲每個點的祖宗節點
      
          // 返回x的祖宗節點
          int find(int x)
          {
              if (p[x] != x) p[x] = find(p[x]);
              return p[x];
          }
      
          // 初始化,假定節點編號是1~n
          for (int i = 1; i <= n; i ++ ) p[i] = i;
      
          // 合并a和b所在的兩個集合:
          p[find(a)] = find(b);
      
          // 查找是否在同一個集合
          find(a) == find(b);
      
      
      (2)維護size的并查集:
      
          int p[N], size[N];
          //p[]存儲每個點的祖宗節點, size[]只有祖宗節點的有意義,表示祖宗節點所在集合中的點的數量
      
          // 返回x的祖宗節點
          int find(int x)
          {
              if (p[x] != x) p[x] = find(p[x]);
              return p[x];
          }
      
          // 初始化,假定節點編號是1~n
          for (int i = 1; i <= n; i ++ )
          {
              p[i] = i;
              size[i] = 1;
          }
      
          // 合并a和b所在的兩個集合:這個有順序,不能錯
          size[find(b)] += size[find(a)];
          p[find(a)] = find(b);
      
      
      (3)維護到祖宗節點距離的并查集:
      
          int p[N], d[N];
          //p[]存儲每個點的祖宗節點, d[x]存儲x到p[x]的距離
      
          // 返回x的祖宗節點
          int find(int x)
          {
              if (p[x] != x)
              {
                  int u = find(p[x]);
                  d[x] += d[p[x]];
                  p[x] = u;
              }
              return p[x];
          }
      
          // 初始化,假定節點編號是1~n
          for (int i = 1; i <= n; i ++ )
          {
              p[i] = i;
              d[i] = 0;
          }
      
          // 合并a和b所在的兩個集合:
          p[find(a)] = find(b);
          d[find(a)] = distance; // 根據具體問題,初始化find(a)的偏移量
      
      

      堆是維護一個數據集合,是用一維數組實現的完全二叉樹(常常用來構建優先隊列,支持堆排序,快速找出一個集合中的最小值或者最大值)
      img

      一維數組存貯
      x的左兒子是2x
      x的右兒子是2x+1
      img
      堆排序
      img
      這些操作都可以用down(){}和up(){}操作表示
      down和up操作是O(logn),求最小值是O(1),插入和刪除都時O(logn)
      將創建堆得操作從O(nlogn) 下降到 O(n),先插入n/2接下來的都down

      Hash表

      搜索和圖論

      DFS與BFS

      DFS

      img

      數據結構 空間 特點
      DFS stack O(h) 不具有最短性 最重要的順序
      BFS queue O(2n) 最短路
      剪枝:
      最優性剪枝:不是最優解,剪枝
      可行性剪枝:不可行時候剪枝

      BFS

      img

      /*
       做這種題目的步驟(最短路問題)
       1.創建兩個存儲,一個存儲值,一個存儲距離
       2.然后首先將第一個點的位置存儲的距離提前標注出來
       3.然后弄兩個方向變量用于上下左右前進int[] dx = {-1,0,1,0}, dy = {0,1,0,-1};
       4.然后如果四個方向上的點沒有超過邊界,在結合實際情況有沒有用過的點,判斷能不能夠進行前進,如果可以就進行前進存儲其內容g跟距離d+1
       5.最后返回想要的最短值d;
      */
      

      樹與圖的遍歷:拓撲排序

      樹是無環聯通的特殊的圖
      圖:無向圖,有向圖

      最短路

      最小生成樹

      二分圖:染色法、匈牙利算法

      數學知識

      質數

      線性篩素數

      const int N = 200000;
      int primes[N];
      int cnt = 0;
      int heshu[N];
      void get_primes(int n){
          for(int i = 2; i < n; i++){
              if(!heshu[i]) primes[cnt++] = i;
              for(int j = 0; primes[j] * i <= n; j++){
                  heshu[primes[j] * i] = true;
                  if(i % primes[j] == 0) break;
              }
          }
      }
      

      1 ≤ n ≤ 106
      時間復雜度O(n)

      約數

      int gcd(int a, int b)
      {
          return b ? gcd(b,a%b):a;
      }
      

      正常

      int gcd(int a, int b){
          if(a%b == 0) return b;
          else return gcd(b,a%b);
      }
      

      歐拉函數

      快速冪

      快速冪迭代版
      求ab mod p

      typedef long long LL;
      int quickmi(int a, int b, int p){//
          int res = 1 %p;
          while(b){//對b進行二進制化,從低位到高位
              if(b & 1) res = (LL) res * a%p;
              //更新a,a依次為a^{2^0},a^{2^1},a^{2^2},....,a^{2^logb}
              a = (LL)a * a%p;
              //b二進制右移一位
              b >>= 1;
          } 
          return res;
      }
      

      b&1就是判斷b的二進制表示中第0位上的數是否為1,若為1,b&1=true,反之b&1=false

      b&1也可以用來判斷奇數和偶數,b&1=true時為奇數,反之b&1=false時為偶數

      判斷二進制第k位的數字為1:n>>k&1 == true;

      擴展歐幾里得算法

      中國剩余定理

      高斯消元

      組合計數

      Cbn = Cbn-1 + Cb-1n-1

      const int N = 2001, MOD = 1e9+7;
      typedef long long LL;
      int C[N][N];
      void init(int n){
          for(int i = 0; i <= n; i++)
          {
              for(int j = 0; j <= i; j++){
                  if(j == 0){C[i][j] = 1;}
                  else C[i][j] = ((LL)C[i-1][j] + C[i-1][j-1])%MOD;
              }
          }
      }
      

      O(n2)

      容斥原理

      img
      從n個數中選擇任意多個數的方案數,共有2n項,時間復雜度2n
      img
      img

      for (int i = 1; i < 1 << m; i ++ )
          //  i<1<<m   組合數  2^m-1  
      
          {
              //t表示所有質數的乘積,cnt表示i里面有幾個一
              int t = 1, s = 0;
      
      
              for (int j = 0; j < m; j ++ )//遍歷二進制的每一位
      
                  if (i >> j & 1)//判斷二進制第j位是否存在
                  {
                      if ((LL)t * p[j] > n)
                      {
                          t = -1;
                          break;
                      }
                      t *= p[j];
                      s ++ ;
                  }
      
              if (t != -1)
              {
      
                  if (s % 2) res += n / t;
                  else res -= n / t;
      
              }
          }
      
      
      
      

      簡單博弈論

      動態規劃

      背包問題

      線性DP

      區間DP

      計數類DP

      數位統計DP

      狀態壓縮DP

      樹形DP

      記憶化搜索

      貪心

      posted on 2022-07-04 14:43  運甓  閱讀(110)  評論(0)    收藏  舉報

      主站蜘蛛池模板: 一区二区三区鲁丝不卡| 亚洲性av网站| 国偷自产一区二区三区在线视频| 91精品人妻中文字幕色| 九九热免费精品在线视频| 精品无码久久久久国产电影| 亚洲国产区男人本色| 加勒比无码人妻东京热| 日韩一区二区大尺度在线| 涿鹿县| 日日噜久久人妻一区二区| 69天堂人成无码免费视频| 精品国产AV最大网站| 国产精品人伦一区二区三| 99riav精品免费视频观看| 国产微拍一区二区三区四区| 正在播放酒店约少妇高潮| 久久精品久久黄色片看看| 国产一区二区三区在线观看免费| 综合区一区二区三区狠狠| 欧洲美女黑人粗性暴交视频| 高级艳妇交换俱乐部小说 | 九九热精品视频在线免费| 最新国产精品中文字幕| 苏州市| 天天做天天爱夜夜爽导航| 97色伦97色伦国产| 国产一区二区三区导航| 人人人澡人人肉久久精品| 东京热大乱系列无码| 四虎在线播放亚洲成人| 漳浦县| 无码中文av波多野结衣一区 | 国产中文字幕精品视频| 国产一区二区三区AV在线无码观看| 国产欧美一区二区日本加勒比 | 国产无遮挡又黄又爽又色| 亚日韩精品一区二区三区| 国产福利深夜在线播放| 国产乱子伦精品免费女| 成av免费大片黄在线观看|