算法基礎學習筆記


基礎算法

前綴和

一維

#include<iostream>
using namespace std;
const int N = 1e6+10;
int q[N],s[N];
int n,m;
int main()
{
    ios::sync_with_stdio(false);//讓cin與標准的輸入不同步,提供cin的讀取速度!
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>q[i];
    for(int i=1;i<=n;i++) s[i]=s[i-1]+q[i];
    while (m -- ){
        int x,y;
        cin>>x>>y;
        cout<<s[y]-s[x-1]<<endl;
    }
}

二維

#include<iostream>
using namespace std;
int n,m,q;
const int N =1010;
int a[N][N],s[N][N];
int main()
{
    cin>>n>>m>>q;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++) 
    {
    cin>>a[i][j];
    s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
    }
    
    while(q--)
    {
        int x1,y1,x2,y2;
        cin>>x1>>y1>>x2>>y2;
        cout<<s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]<<endl;
    }
    
}

差分

一維

#include<iostream>
const int N = 100010;
using namespace std;
int l,r,c;
int n,m;
int a[N],b[N];
void insert(int l,int r,int c){
    b[l]+=c;
    b[r+1]-=c;
}
int main()
{
    
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++) insert(i,i,a[i]);
    while (m -- ){
        cin>>l>>r>>c;
        insert(l,r,c);
    }
      for(int i=1;i<=n;i++) b[i]+=b[i-1];
      for(int i=1;i<=n;i++) cout<<b[i]<<" ";
}

二維

#include<iostream>
using namespace std;
const int N=1010;
int m,n,q;
int a[N][N],b[N][N];
int x1,y1,x2,y2,c;
void insert(int x1,int y1,int x2,int y2,int c){
    b[x1][y1]+=c;
    b[x1][y2+1]-=c;
    b[x2+1][y1]-=c;
    b[x2+1][y2+1]+=c;
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m>>q;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    cin>>a[i][j];
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    insert(i,j,i,j,a[i][j]);
    
    
    while(q--)
    {
        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-1][j]+b[i][j-1]-b[i-1][j-1];
    }
    for(int i=1;i<=n;i++){
    for(int j=1;j<=m;j++){
    cout<<b[i][j]<<" ";
    }
    cout<<endl;
    }
}

二分

整數的二分

#include<iostream>
using namespace std;
const int N=1e5+10;
int n,k,q;
int a[N];
int main()
{
  ios::sync_with_stdio(false);
  cin>>n>>q;
  for(int i=0;i<n;i++) cin>>a[i];
  
  while(q--){
    int l=0,r=n-1;
    cin>>k;
    while(l<r){
    int mid=l+r>>1;
    if(a[mid]>=k) r=mid;
    else l=mid+1;
}
  if(a[l]!=k) cout<<"-1 -1"<<endl;
  else{
    cout<<l<<" ";
    l=0,r=n-1;
    while(l<r){
      int mid=l+r+1>>1;
      if(a[mid]<=k) l=mid;
      else r=mid-1;
    }
    cout<<l<<endl;
}
}
}

浮點數的二分

#include<iostream>
using namespace std;
int main(){
    double n;
    cin>>n;
    double l=-100;
    double r=100;
    while(r-l>1e-8){
        double mid=(l+r)/2;
        if(mid*mid*mid>=n) r=mid;
        else l=mid;
    }
    cout<<l<<endl;
}

離散化+前綴和

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N=300010;
int a[N],s[N];
typedef pair<int,int> PII;
vector<int>alls;
vector<PII>add,query;
int find(int 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;
}
int n,m;
int main(){
      cin>>n>>m;
      for(int i=0;i<n;i++){
          int x,c;
          cin>>x>>c;
          alls.push_back(x);
          add.push_back({x,c});
      }
      while(m--){
          int l,r;
          cin>>l>>r;
          query.push_back({l,r});
          alls.push_back(l);
          alls.push_back(r);
      }
      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=1;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;    
      }
}

vector<int>::iterator unique(vector<int> &a){
   //雙指針用法
   for(int i=0,j=0;i<a.size();i++)
   if(!i||a[i]!=a[i-1])//check判定條件
   {
      a[j]=a[i];
        j++;
   }
   return a.begin()+j;
}

區間合並

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
typedef pair<int,int>PII;
int n;
void merge(vector<PII> &se){
     vector<PII>res;//存不重合的區間個數,最后更新一下即可
     sort(se.begin(),se.end());//以左端點進行排序
     int st=-2e9,ed=-2e9;

     for(auto s:se)
         if(ed<s.first){//兩種情況 ,無交集和有交集
            if(st!=-2e9) res.push_back({st,ed});
            st=s.first,ed=s.second;
         }
         else ed=max(ed,s.second);//有交集


         if(st!=-2e9) res.push_back({st,ed});//最后一個區間放入res中
         se=res;//更新一下
 }
int main(){
    vector<PII>se;
    ios::sync_with_stdio(false);
    cin>>n;
    for(int i=0;i<n;i++){
        int l,r;
        cin>>l>>r;
        se.push_back({l,r});
    }
    merge(se);
    cout<<se.size()<<endl;
    return 0;
}

基礎數據結構

模擬棧

// tt表示棧頂
int stk[N], tt = 0;

// 向棧頂插入一個數
stk[ ++ tt] = x;

// 從棧頂彈出一個數
tt -- ;

// 棧頂的值
stk[tt];

// 判斷棧是否為空
if (tt > 0)
{

}

模擬隊列

//普通隊列
// hh 表示隊頭,tt表示隊尾
int q[N], hh = 0, tt = -1;

// 向隊尾插入一個數
q[ ++ tt] = x;

// 從隊頭彈出一個數
hh ++ ;

// 隊頭的值
q[hh];

// 判斷隊列是否為空
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];

// 判斷隊列是否為空
if (hh != tt)
{

}

單調棧

#include<iostream>
using namespace std;
const int N=10010;
int n;
int stk[N],tt;
int main(){
    cin.tie(0);
    cin>>n;
    while(n--){
     int x;
     cin>>x;
     while(tt&&stk[tt]>=x) tt--;
     if(tt) cout<<stk[tt]<<" ";
     else cout<<"-1"<<" ";
     stk[++tt]=x;
    }
}
#include<iostream>
#include<stack>
using namespace std;
int n;
stack<int>skt;
int main(){
    cin.tie(0);
    cin>>n;
    for(int i=0;i<n;i++)
    {
        int x;
        cin>>x;
        while(!skt.empty()&&skt.top()>=x) skt.pop();
        if(!skt.empty()) cout<<skt.top()<<" ";
        else cout<<"-1"<<" ";
        skt.push(x);
    }
}

單調隊列 滑動窗口

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e6+10;
int n,k;
int a[N],q[N];
int main()
{
    //min
    scanf("%d%d", &n,&k);
    for (int i = 0; i < n; i ++ ) cin>>a[i];
    int hh=0,tt=-1;//表頭和表尾
    for (int i = 0; i < n; i ++ )
    {
        //判斷起點是否已經划出窗口
        if(hh<=tt&&i-k+1>q[hh])hh++;
        while(hh<=tt&&a[q[tt]]>=a[i]) tt--;
        q[++tt]=i;
        if(i>=k-1) printf("%d ",a[q[hh]]);
    }
    puts("");
    //max
    hh=0,tt=-1;//表頭和表尾
    for (int i = 0; i < n; i ++ )
    {
        //判斷起點是否已經划出窗口
        if(hh<=tt&&i-k+1>q[hh])hh++;
        while(hh<=tt&&a[q[tt]]<=a[i]) tt--;
        q[++tt]=i;
        if(i>=k-1) printf("%d ",a[q[hh]]);
    }
    puts("");
    
}

KMP

朴素匹配算法

//KMP朴素匹配算法
for(int i=1;i<=n;i++)
 bool flag=true;
 for(int j=1;j<=m;j++)
 {
     if(s[i]!=p[j])
     {
         flag=false;
         break;
     }
 }

KMP字符串

理解起來很抽象,好不容易理解清楚,后面還會多寫幾次

#include<iostream>
using namespace std;
const int N=100010,M=1000010;
int n,m;
char p[N],s[M];
int ne[N];
int main(){
    cin>>n>>p+1>>m>>s+1;
    //求next
    for(int i=2,j=0;i<=n;i++)
    {
        while(j&&p[i]!=p[j+1]) j=ne[j];
        if(p[i]==p[j+1]) j++;
        ne[i]=j;
    }
    //kmp 匹配過程
    for(int i=1,j=0;i<=m;i++)
    {
         while(j&&s[i]!=p[j+1]) j=ne[j];
         if(s[i]==p[j+1]) j++;
         if(j==n)  {
             printf("%d ",i-n);
             j=ne[j];
         }
    }
    return 0;
}

trie

概念

trie是一種具有存儲和查詢功能字符串的數據結構

acwing 835 字符串統計

題目鏈接
對應代碼的son[p][u]代表存儲着某個節點的子節點序號,樹中的節點是一個一個分配的,idx用來表示當前分配到了第幾個位置,因為題目只針對小寫英文字母,所以一個節點的子節點最多26個,cnt[]用來表示當前節點出現的次數,不理解的自己手動進行模擬一下就清楚了

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 10010;
int son[N][26],idx,cnt[N];
char str[N];
void insert(char *str){
    int p=0;
    for(int i=0;str[i];i++){
        int u=str[i]-'a';
        if(!son[p][u]) son[p][u]=++idx;
        p=son[p][u];
    }
    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];
}
int main()
{
    int n;
    cin>>n;
    while(n--){
       char c;
       cin>>c>>str;
       if(c=='I') insert(str);
       else cout<<query(str)<<endl;
    }
    return 0;
}

詳解

並查集

優化

路徑壓縮
按秩合並

並查集+路徑壓縮

簡單並查集

#include <iostream>
#include <cstring>
#include <algorithm>
const int N = 1e5+10;
using namespace std;
int n,m;
int p[N];
int find(int x)  // 並查集+路徑壓縮
{
  //不是根節點
  if(p[x]!=x)   p[x]=find(p[x]);//路徑壓縮讓父節點指向根節點
  return p[x];
}
int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++) p[i]=i;//初始化
    while (m -- ){
        char op[2];
        int a,b;
        scanf("%s%d%d", op,&a,&b);
        if(*op=='M') p[find(a)]=find(b); //a節點的祖宗節點的父節點指向b的祖宗節點
        else if(*op=='Q'){
            if(find(a)==find(b))
             puts("Yes");
             else puts("No");
        }
    }
}

存每個點的個數的並查集+路徑壓縮

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
int n,m,a,b;
int p[N],cnt[N];
int find(int x)  // 並查集
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}
int UN(int a,int b){
    cnt[find(b)]+=cnt[find(a)];
    p[find(a)]=find(b);
}
void QY(int a,int b){
    if(find(a)==find(b)) puts("Yes");
    else puts("No");
}
int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)
    {
        cnt[i]=1;
        p[i]=i;
    }
    while (m -- ){
        string s;
        cin>>s;
        if(s=="C"){
            cin>>a>>b;
            if(find(a)==find(b)) continue;
            UN(a,b);
        }
        else if(s=="Q1"){
            cin>>a>>b;
            QY(a,b);
        }
        else{
            cin>>a;
            cout<<cnt[find(a)]<<endl;
        }
    }
}

思路

圖片為截取!后面忘了方便回顧。
並查集
並查集

哈希表

特殊哈希表 離散化

一般哈希表 開放地址法和拉鏈法

#include<iostream>
#include<cstring>
using namespace std;
const int N=200003,INF=0x3f3f3f3f;
int h[N],x;
int find(int x){
    int k=(x%N+N)%N;
    while(h[k]!=INF&&h[k]!=x)
    {
          k++; 
          if(k==N) k=0;
    }
    return k;
}
int main(){
     int n;
     cin>>n;
     memset(h, 0x3f, sizeof h);

     while(n--){
     char op[2];
     scanf("%s%d",op,&x);
     if(*op=='I'){
         h[find(x)]=x;
     }
     else {
         if(h[find(x)]!=INF)  puts("Yes");
         else puts("No");
     }
}
}

字符串哈希

主要用於解決一個字符串中 某兩個區間是否存在相同的字符串或者輸出某個字符串區間對應的hash值

#include <iostream>
#include <cstring>
#include <algorithm>
const int N = 1e5+10;
using namespace std;
typedef unsigned long long ULL;
ULL h[N],p[N];
int P=131,n,m;
char str[N];
int get(int l,int r){
    return h[r]-h[l-1]*p[r-l+1];
}
int main()
{
    cin.tie(0);
    cin>>n>>m;
    cin>>str+1;
    p[0]=1;
    for(int i=1;i<=n;i++){
        p[i]=p[i-1]*P;
        h[i]=h[i-1]*P+str[i];
    }
    while (m -- ){
        int l1,r1,l2,r2;
        cin>>l1>>r1>>l2>>r2;
        if(get(l1,r1)==get(l2,r2)) puts("Yes");
        else puts("No");
    }
}

思路

沖突問題:P=131,13331時,Q=2的64次方時,可99.9%避免沖突問題,可以用unsigned long long 代替Q 2的64 溢出自動取模

STL

vector(變長數組),倍增的思想,支持比較運算(按字典序)
#### 支持比較運算
比如:vector<int>a(4,3),b(3,4) if(a<b) puts("yes")  3333<4444 按字典序比較
    定義::
        vector <int> a; 定義:一個vector數組a
        vector <int> a(10); 定義:一個長度為10的vector數組a
        vector <int> a(10,3); 定義:一個長度為10的vector數組a,並且所有元素都為3
    常用函數::
        size(); 返回元素個數
        empty(); 返回是否是空
        clear(); 清空
        front(); 返回vector的第一個數
        back(); 返回vector的最后一個數
        push_back(); 向vector的最后插入一個數
        pop_back(); 把vector的最后一個數刪掉
        begin(); vector的第0個數
        end(); vector的最后一個的數的后面一個數
    倍增的思想:
        系統為某一程序分配空間是,所需時間,與空間大小無關,與申請次數有關
    遍歷方法
        假設有個vector <int> a;
        第一種:
            for(int i = 0;i < a.size();i ++) cout<<a[i]<<" ";
        第二種:
            for(vector <int>::iterator i = a.begin();i != a.end();i ++) cout<<*i<<" ";  vector <int>::iterator可以寫為auto
        第三種:
            for(auto  x : a) cout<<x<<" ";



pair,支持比較運算,以first為第一關鍵字,以second為第二關鍵字(按字典序)
    定義::
        pair <類型,類型> 變量名;    兩個類型可以不同
    初始化方式:
        假設有個pair <int,string> p;
        第一種:
            p = make_pair(10,"abc");
        第二種:
            p = {10,"abc");
    常用函數::
        first(); 第一個元素
        second(); 第二個元素

string(字符串)
    常用函數::
        substr(起始位置,長度(長度可省略)); 返回每一個子串
        c_str(); 返回這個string對應的字符數組的頭指針
        size(); 返回字母個數
        length(); 返回字母個數
        empty(); 返回字符串是否為空
        clear(); 把字符串清空
queue(隊列)
    定義::
        queue <類型> 變量名;
    常用函數::
        size(); 這個隊列的長度
        empty(); 返回這個隊列是否為空
        push(); 往隊尾插入一個元素
        front(); 返回隊頭元素
        back(); 返回隊尾元素
        pop(); 把隊頭彈出
        注意:隊列沒有clear函數!!!
    清空:
        變量名 = queue <int> ();
priority_queue(優先隊列,堆)
    注意:默認是大根堆!!!
    定義::
        大根堆:priority_queue <類型> 變量名;
        小根堆:(1)priority_queue <類型,vecotr <類型>,greater <類型>> 變量名
               (2)大根堆插入的時候插入相反數
    常用函數:
        size(); 這個堆的長度
        empty(); 返回這個堆是否為空
        push();往堆里插入一個元素
        top(); 返回堆頂元素
        pop(); 彈出堆頂元素
        注意:堆沒有clear函數!!!

stack(棧)
    常用函數:
        size(); 這個棧的長度
        empty(); 返回這個棧是否為空
        push(); 向棧頂插入一個元素
        top(); 返回棧頂元素
        pop(); 彈出棧頂元素

deque(雙端隊列)
    常用函數:
        size(); 這個雙端隊列的長度
        empty(); 返回這個雙端隊列是否為空
        clear(); 清空這個雙端隊列
        front(); 返回第一個元素
        back(); 返回最后一個元素
        push_back(); 向最后插入一個元素
        pop_back(); 彈出最后一個元素
        push_front(); 向隊首插入一個元素
        pop_front(); 彈出第一個元素
        begin(); 雙端隊列的第0個數
        end(); 雙端隊列的最后一個的數的后面一個數

set,map,multiset,multimap 基於平衡二叉樹(紅黑樹),動態維護有序序列
    set/multiset
        注意:set不允許元素重復,如果有重復就會被忽略,但multiset允許!!!
        常用函數:
            size(); 返回元素個數
            empty(); 返回set是否是空的
            clear(); 清空
            begin(); 第0個數,支持++或--,返回前驅和后繼
            end(); 最后一個的數的后面一個數,支持++或--,返回前驅和后繼
            insert(); 插入一個數
            find(); 查找一個數
            count(); 返回某一個數的個數
            erase();
                (1)輸入是一個數x,刪除所有x    O(k + log n) k==x的個數
                (2)輸入一個迭代器,刪除這個迭代器
            lower_bound(x); 返回大於等於x的最小的數的迭代器 
            upper_bound(x); 返回大於x的最小的數的迭代器
    map/multimap
        常用函數:
            insert(); 插入一個數,插入的數是一個pair
            erase(); 
                (1)輸入是pair
                (2)輸入一個迭代器,刪除這個迭代器
            find(); 查找一個數
            lower_bound(x); 返回大於等於x的最小的數的迭代器
            upper_bound(x); 返回大於x的最小的數的迭代器

unordered_set,unordered_map,unordered_muliset,unordered_multimap 基於哈希表
    和上面類似,增刪改查的時間復雜度是O(1)
    不支持lower_bound()和upper_bound()

bitset 壓位
    定義:
        bitset <個數> 變量名;
    支持:
        ~,&,|,^
        >>,<<
        ==,!=
        []
    常用函數:
        count(); 返回某一個數的個數
        any(); 判斷是否至少有一個1
        none(); 判斷是否全為0
        set(); 把所有位置賦值為1
        set(k,v); 將第k位變成v
        reset(); 把所有位變成0
        flip(); 把所有位取反,等價於~
        flip(k); 把第k位取反


數論

質數

判斷素數

最好不寫成i<=sqrt(n)這種方式,每次求sqrt時間復雜度增加,i*i<=n會有溢出問題
// 判斷質數
普通寫法:時間復雜度O(n)

bool is_prime(int n){
if(n<2) return false;
for(int i=2;i<n;i++){
  if(n%i==0) return false;
}
return true;
}

優化:時間復雜度O(sqrt(n))

bool is_prime(int n){
    if(n<2) return false;
    for(int i=2;i<=n/i;i++)
    {
        if(n%i==0) return false;
    }
    return true;
}

分解質因數

// 分解質因數
void divide(int x){
    for(int i=2;i<=n;i++){
        if(n%i==0) //i一定是質數
        int s=0;
        while(n%i==0){
            n/=i;
            s++;
        }
        printf("%d %d\n",i,s);
    }
}

優化:n中最多只包含一個大於\(\sqrt{n}\)的質因子,所以枚舉的時候可以先把所有小於sqrt(n)的質因子枚舉出來,如果最后n>1,說明剩下的這個數就是大於sqrt(n)的質因子 ,輸出n和次數1 這樣復雜度就可以有效的從O(n)降低到O(sqrt(n)) 最快O(log(n)) 最慢O(sqrt(n))

void divide(int n){
    for(int i=2;i<=n/i;i++){
        if(n%i==0){
            int s=0;
            while(n%i==0){
                n/=i;
                s++;
            }
            printf("%d %d\n",i,s);
        }
    }
    if(n>1) printf("%d %d\n",n,1);
    puts("");
}

篩質數

朴素

朴素篩法:對大於2的每一個數都進行倍數刪除,剩下的數就是素數,但大概時間復雜度為O(n)!

步驟:把 [2,n−1][2,n−1] 中的所有的數的倍數都標記上,最后沒有被標記的數就是質數.
原理:假定有一個數p未被 [2,p−1][2,p−1] 中的數標記過,那么說明,不存在 [2,p−1][2,p−1] 中的任何一個數的倍數是p,也就是說 [2,p−1][2,p−1] 中不存在p的約數,因此,根據質數的定義可知p是質數.
調和級數:當n趨近於正無窮的時候,1/2+1/3+1/4+1/5+…+1/n=lnn+c1/2+1/3+1/4+1/5+…+1/n=ln⁡n+c( cc 是歐拉常數,約等於 0.5770.577 左右)
時間復雜度:約為 O(nlogn)(注:此處的 loglog 特指以2為底)

void get_prime(int n){
    for(int i=2;i<=n;i++){
        if(!st[i]) prime[cnt++]=i;//把沒有被刪的數放進去,說明它是質數
        for(int j=i+i;j<=n;j+=i) st[j]=true; //刪除翻倍的i
    }
}

埃氏篩法

埃氏篩法,時間復雜度(O(nloglogn)),比普通篩法快3倍左右。
質數唯一分解定理:每個大於1的自然數,要么本身就是質數,要么可以寫為2個或以上的質數的積
優化:每一次只對素數進行的倍數進行刪除,當一個數不是質數的時候,就不需要進行倍數刪除(因為質數唯一分解定理)

質數定理:1~n1~n 中有n/lnn個質數.
步驟:在朴素篩法的過程中只用質數項去篩.
時間復雜度:O(nlog(logn))。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e6+10;
int prime[N],n,cnt;
bool st[N];
void get_prime(int n){
    for(int i=2;i<=n;i++){
        if(!st[i]) {
            prime[cnt++]=i;
            for(int j=i+i;j<=n;j+=i) st[j]=true; //每一次只刪除素數的倍數
        }
    }
}
int main()
{
    cin>>n;
    get_prime(n);
    cout<<cnt<<endl;
}

線性篩法

若n≈106,線性篩和埃氏篩的時間效率差不多,若 n≈107,線性篩會比埃氏篩快了大概一倍。
核心:1~n1~n 內的合數 p只會被其最小質因子篩掉。(算數基本定理)
原理:1~n1~n 之內的任何一個合數一定會被篩掉,而且篩的時候只用最小質因子來篩,然后每一個數都只有一個最小質因子,因此每個數都只會被篩一次,因此線性篩法是線性的.
枚舉到 i 的最小質因子的時候就會停下來,即 if (i % primes[j] == 0) break
當i%primes[j] != 0時,primes[j]一定小於i的最小質因子,primes[j]一定是primes[j]*i的最小質因子
當i% primes[j] == 0 時,primes[j] 一定是i的最小質因子,而primes[j]又是primes[j]的最小質因子,因此 primes[j]是primes[j]*i的最小質因子

int primes[N], cnt;     // primes[]存儲所有素數,cnt為質數的個數
 bool st[N];         // st[x]存儲x是否被篩掉
  void get_primes(int n)
  {
      for (int i = 2; i <= n; i ++ )
      {
          if (!st[i]) primes[cnt ++ ] = i;
          // j < cnt 不必要,因為 primes[cnt - 1] = 當前最大質數
          // 如果 i 不是質數,肯定會在中間就 break 掉
          // 如果 i 是質數,那么 primes[cnt - 1] = i,也保證了 j < cnt
          for (int j = 0; primes[j] <= n / i; j ++ )
          {
              st[primes[j] * i] = true;
              if (i % primes[j] == 0) break;
          }
      }
  }

約數

求所有的約數

#include<iostream>
#include <vector>
#include <algorithm>

using namespace std;
vector<int>get_num(int x){
    vector<int>res;
    for(int i=1;i<=x/i;i++){
        if(x%i==0){
        res.push_back(i);
        if(i!=x/i) res.push_back(x/i);
        }
    }
    sort(res.begin(),res.end());
    return res;
}
int main()
{

    int n;
    cin>>n;
    while (n -- ){
        int x;
        cin>>x;
        auto num=get_num(x);
        for(auto it:num){
            cout<<it<<" ";
        }
        puts("");
    }
}

約數個數

約數個數和約數之和是一類題,不能用求所有約數的試除法那樣,會有數據溢出問題,只需要記住並理解相應的公式即可
約數個數:先求每個數的分解質因數,將底數和指數放在一個unordered_map中,然后開一個long long res=1;然后按照公式即可

約數之和: p為底數,上標為指數,

可以考慮等比數列求和公式,但是會有溢出問題 sum=a1*(1-q^n)/1-q

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>
typedef long long LL;
using namespace std;
const int mod=1e9+7;
int main()
{
    int n;
    cin>>n;
    unordered_map<int,int>q;
    while (n -- ){
        int x;
        cin>>x;
        for(int i=2;i<=x/i;i++){
            while(x%i==0){
                x/=i;
                q[i]++;
            }
        }
        if(x>1) q[x]++;
    }
    LL res=1;
    for(auto it:q) res=res*(it.second+1)%mod;
    cout<<res<<endl;
    return 0;
}

約數之和

公式:sum=(\(p_0\)+....+\(p_a1\))(\(p_0\)+....+\(p_a2\))...*(\(p_0\)+....+\(p_ak\))

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>
typedef long long LL;
using namespace std;
const int mod=1e9+7;
int main()
{
    int n;
    cin>>n;
    unordered_map<int,int>q;
    while (n -- ){
        int x;
        cin>>x;
        for(int i=2;i<=x/i;i++){
            while(x%i==0){
                x/=i;
                q[i]++;
            }
        }
        if(x>1) q[x]++;
    }
    LL res=1;
    for(auto s:q){
        int x=s.first; //x為底數
        int y=s.second; //y為指數 
        LL t=1;
        while(y--) t=(t*x+1)%mod;
        res=res*t%mod;
    }
    cout<<res<<endl;
    return 0;
}

GCD最大公約數

適用整數,時間復雜度: O(n),n 為最大數字位數

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

#include<iostream>
int gcd(int a,int b) {
	return __gcd(a,b);
}

適用於小數,EPS 為精度控制

double gcd(double a,double b){ 
return a<EPS?b:gcd(fmod(b,a),a);
 }

歐拉函數

單數歐拉

y(n):1到n中與n互質的數
以下為求的公式以及推理(容斥原理)

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int n;
int main()
{
    cin>>n;
    while (n -- ){
        int x;
        cin>>x;
        int res=x;
        for(int i=2;i<=x/i;i++){
            if(x%i==0){
                res=res/i*(i-1);//處理小數問題
                while(x%i==0){
                    x/=i;
                }
            }
        }
        if(x>1) res=res/x*(x-1);
           cout<<res<<endl;
    }
    return 0;
}

線性篩法

適用於求1~n歐拉函數之和

phi[N]為歐拉函數
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=1000010;
int primes[N],cnt;
bool st[N];
int phi[N];//歐拉函數
LL get_euler(int n){
    phi[1]=1;
     for(int i=2;i<=n;i++){
          if(!st[i]) {
              primes[cnt++]=i;
              phi[i]=i-1;
          }
         
          for(int j=0;primes[j]<=n/i;j++){
              st[primes[j]*i]=true;
              if(i%primes[j]==0){
                  phi[primes[j]*i]=primes[j]*phi[i];
                  break;
              }
              else{
                  phi[primes[j]*i]=phi[i]*(primes[j]-1);
              } 
          }
     }
     LL res=0;
     for(int i=1;i<=n;i++){
         res+=phi[i];
     }
     return res;
}
int main(){
       int n;
       cin>>n;
       cout<<get_euler(n)<<endl;
       return 0;
}

歐拉定理

若a與n互質
a y(n)=1(mod p)

費馬定理

當n為質數的時候
y(n)=n-1
a n-1=1(mod p)

高斯消元

高次同余方程

容斥原理

中國剩余定理

擴展歐幾里得定理

排列組合

特殊數之卡特蘭數

快速冪

康拓擴展

莫比烏斯反演

博弈論

圖論

DFS

回溯剪枝

//DFS(回溯和剪枝)
剪枝:1.當當前的路徑一定可以判斷不如最優解的時候,就可以剪掉。
2.可行性剪枝,當當前的路徑不合法就可以剪枝

n皇后問題(遍歷行)

時間復雜度:O(n^2)

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 20;
int col[N],dg[N],udg[N];
int n;
char g[N][N];
void dfs(int u){
    if(u==n){
        for (int i = 0; i < n; i ++ ) puts(g[i]);
        puts("");
        return;
    }
    for (int i = 0; i < n; i ++ ){
        if(!col[i]&&!dg[u+i]&&!udg[n-u+i]){
            g[u][i]='Q';
            col[i]=dg[u+i]=udg[n-u+i]=1;
            dfs(u+1);
            col[i]=dg[u+i]=udg[n-u+i]=0;
            g[u][i]='.';
        }
    }
}
int main()
{
    cin>>n;
    for (int i = 0; i < n; i ++ )
     for (int j = 0; j < n; j ++ )
           g[i][j]='.';
    dfs(0);
    return 0;
}

n皇后問題(遍歷列)

最直接的做法,時間復雜度為O(2^n)

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 20;
int col[N],row[N],dg[N],udg[N];
int n;
char g[N][N];

void dfs(int x,int y,int s){
    if(y==n) y=0,x++;
    
    if(x==n) //代表走到最后一行
    {
        if(s==n){//放了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]){
        row[x]=col[y]=dg[x+y]=udg[x-y+n]=1;
        g[x][y]='Q';
        dfs(x,y+1,s+1);
        row[x]=col[y]=dg[x+y]=udg[x-y+n]=0;
        g[x][y]='.';
    }
}
int main()
{
    cin>>n;
    for (int i = 0; i < n; i ++ )
     for (int j = 0; j < n; j ++ )
           g[i][j]='.';
    dfs(0,0,0);
    return 0;
}

BFS

最短路

BFS主要用來搜最短路

走迷宮

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
int n,m;
const int N = 110;
int g[N][N],d[N][N];//每個點到起點的距離

int bfs(){
    queue<PII>q;
    memset(d,-1,sizeof d);
    q.push({0,0});
    d[0][0]=0;
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};//向上 向右 向下 向左  4方向偏移量

    while(!q.empty()){

        auto t=q.front(); 
        q.pop();
        for(int i=0;i<4;i++)
        {
            int x=t.first+dx[i];
            int y=t.second+dy[i];
            if(x>=0&&x<n&&y>=0&&y<m&&g[x][y]==0&&d[x][y]==-1)//d[x][y]=-1代表還沒有走過
            {//它已經走過的話,說明這個點不是第一次搜到,bfs是第一次搜到的點是最短距離,否則不是最短距離
                d[x][y]=d[t.first][t.second]+1;
                q.push({x,y});
            }
        }
    }
    return d[n-1][m-1];

}
int main()
{
    cin >> n>>m;
     for (int i = 0; i < n; i ++ )
       for (int j = 0; j < m; j ++ )
           cin>>g[i][j];

    cout<<bfs()<<endl;
}

存最短路徑的坐標

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
int n,m;
const int N = 110;
int g[N][N],d[N][N];//每個點到起點的距離
PII pre[N][N];//存每個路徑
int bfs(){
    queue<PII>q;
    memset(d,-1,sizeof d);
    q.push({0,0});
    d[0][0]=0;
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};//向上 向右 向下 向左  4方向偏移量
    
    while(!q.empty()){
        
        auto t=q.front(); 
        q.pop();
        for(int i=0;i<4;i++)
        {
            int x=t.first+dx[i];
            int y=t.second+dy[i];
            if(x>=0&&x<n&&y>=0&&y<m&&g[x][y]==0&&d[x][y]==-1)//d[x][y]=-1代表還沒有走過
            {//它已經走過的話,說明這個點不是第一次搜到,bfs是第一次搜到的點是最短距離,否則不是最短距離
                d[x][y]=d[t.first][t.second]+1;
                pre[x][y]=t;
                q.push({x,y});
            }
        }
    }
    int x=n-1,y=m-1;
    
    while(x||y)
    {
        cout<<x<<" "<<y<<" "<<endl;
        auto t=pre[x][y];
        x=t.first,y=t.second;
    }
    return d[n-1][m-1];

}
int main()
{
    cin >> n>>m;
     for (int i = 0; i < n; i ++ )
       for (int j = 0; j < m; j ++ )
           cin>>g[i][j];
         
    cout<<bfs()<<endl;
}

樹與圖的廣度優先遍歷

樹與圖的深度優先遍歷

拓撲排序

Dijkstra算法

bellman-ford算法

SPFA算法

Floyd算法

Prim算法

Kruskal算法

二分圖

匈牙利算法

動態規划dp

背包問題

01背包問題



特點:每件物品最多只用一次
背包:N個物品 V容量
​ 每個物品體積Vi 權重WI

朴素

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N];
int f[N][N];
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    //初始化枚舉所有的狀態f[0-n][0-m],   f[0][0-m]=0
    for(int i=1;i<=n;i++)
    for(int j=0;j<=m;j++){
        //左邊集合 不包含第i個物品  一定存在
        f[i][j]=f[i-1][j];
        //右邊集合  不一定存在 當當前的j裝不下vi的時候就是空集 j<vi
        if(j>=v[i]){
            f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
        }
    }
    
    cout<<f[n][m]<<endl;
    return 0;
}

一維優化(滾動數組)

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N];
int f[N];
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    
    // for(int i=1;i<=n;i++)
    // for(int j=v[i];j<=m;j++){
    //     // 當j<vi的時候是沒有意義的 if(j>=v[i]可以去掉)
    //         f[j]=max(f[j],f[j-v[i]]+w[i]); //這一步j-v[i]是嚴格小於j的 他會在第i層j之前被計算 表示的就是i f[i][j-v[i]]+w[i]
    // }
    
    for(int i=1;i<=n;i++)
    for(int j=m;j>=v[i];j--){ //只需要每次從m往前枚舉 就可以避免j-v[i]被計算
            
            f[j]=max(f[j],f[j-v[i]]+w[i]);
    }
    cout<<f[m]<<endl;
    return 0;
}

完全背包問題

朴素

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
int n,m;
int v[N],w[N];
int main()
{
    //朴素做法 時間復雜度最壞10^9次方 可以優化
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    
    for(int i=1;i<=n;i++)
      for(int j=0;j<=m;j++){
          for(int k=0;k*v[i]<=j;k++){
              f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k); //不選i和選i合並在一起  因為k=0 也滿足不選i的寫法
          }
      }
      cout<<f[n][m]<<endl;
      return 0;
}

二維優化

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
int n,m;
int v[N],w[N];
int main()
{

    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    
    for(int i=1;i<=n;i++)
      for(int j=0;j<=m;j++){
              f[i][j]=f[i-1][j];
              if(j>=v[i]) f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
      }
       
      cout<<f[n][m]<<endl;
      return 0;
}

一維優化

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N];
int n,m;
int v[N],w[N];
int main()
{

    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
   
    for(int i=1;i<=n;i++)
      for(int j=v[i];j<=m;j++){
          //一維每次用到的都是上一層的狀態  這里不能倒着枚舉  因為此時的i就代表i 不代表i-1
             f[j]=max(f[j],f[j-v[i]]+w[i]);
      }
       
      cout<<f[m]<<endl;
      return 0;
}

多重背包問題

朴素

/*
 
 f[i][j]=max(f[i-1[j-v[i]*k+w[i]*k)  k=0,1,2,3,.....,s[i];
*/
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 101;
int v[N],w[N],s[N];
int f[N][N];
int n,m;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>v[i]>>w[i]>>s[i];
    } 
    for(int i=1;i<=n;i++)
      for(int j=0;j<=m;j++){
          for(int k=0;k*v[i]<=j&&k<=s[i];k++){
              f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);
          }
      }
      cout<<f[n][m]<<endl;
}

二進制優化

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 13000;
int n,m;
int v[N],w[N];
int f[N];
int main()
{
    cin>>n>>m;
    int cnt=0;//分組的組別
    for(int i=1;i<=n;i++){
        int a,b,s;
        cin>>a>>b>>s;
        int k=1;//每一個組別的個數
        while(k<=s){
            cnt++;
            v[cnt]=a*k;//整體的價值
            w[cnt]=b*k;//整體的體積
            s-=k;
            k=k*2;
        }
        if(s>0){ //最后的C
            cnt++;
            v[cnt]=a*s;
            w[cnt]=b*s;
        }
    }
    n=cnt;
    
    for(int i=1;i<=n;i++)
     for(int j=m;j>=v[i];j--){
         f[j]=max(f[j],f[j-v[i]]+w[i]);
     }
     cout<<f[m]<<endl;
}

分組背包問題

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int n,m;
int v[N][N],w[N][N],s[N];//表示第 i 個物品組的物品數量
int f[N];
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin >> s[i];
        for(int j=0;j<s[i];j++)
            cin >> v[i][j]>>w[i][j];
    }

    for(int i=1;i<=n;i++)  //遍歷每一個組別
      for(int j=m;j>=0;j--)
        for(int k=0;k<s[i];k++)//遍歷一個k個物品 
        {
            if(j>=v[i][k]){
            f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
            }
        }
        cout<<f[m]<<endl;
}

線性DP

區間DP

計數類DP

數位統計DP

狀態壓縮DP

樹形DP

記憶化搜索


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM