算法基础学习笔记


基础算法

前缀和

一维

#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