【Top】基础算法模板


摘要

本文所涉及到的算法和数据结果模板均来自于Acwing的算法基础课算法提高课(部分)。本文仅给出算法代码与时空复杂度,具体证明和讲解暂无。

大数运算

#include
#include
#include
using namespace std;
//由低位到高位储存 114514 -> {4 1 5 4 1 1}
typedef vector bign;

//大正整数计算


//判断a>=b
bool cmp(bign& a, bign& b){
    if(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;
    }
    return a.size()>b.size();
}

bign add(bign& a, bign& b){
    bign r;

    int t = 0;
    for(int i=0;i=B
bign sub(bign& a, bign& b){
    bign r;

    for(int i=0,t=0;i1&&r.back()==0)r.pop_back();
    return r;
}

//大整数与小整数乘法
bign mul(bign& a, int b){
    bign r;

    int t=0;
    for(int i=0;i1&&r.back()==0)r.pop_back();
    return r;
}

#include
//大整数与小整数的除法
// c是余数,通过引用回传
bign div(bign& a, int b, int& c){
    bign r;
    c=0;
    for(int i=a.size()-1;i>=0;i--){
        c = c*10+a[i];
        r.push_back(c/b);
        c%=b;        
    }
    
    reverse(r.begin(),r.end());
    while(r.size()>1&&r.back()==0)r.pop_back();
    return r;
}

基础排序

归并排序

  • 时间复杂度 \(O(n\log n)\)
//归并排序 稳定算法
#include
using namespace std;

const int len = 1000;

int help[len];
void mergeSort(int nums[], int l, int r){
    if(l>=r)return ;
    int mid = l+r>>1;

    mergeSort(nums,l,mid);
    mergeSort(nums,mid+1,r);

    int top = 0,i=l,j=mid+1;
    
    while(i<=mid&&j<=r)
        if(nums[i]

快速排序

  • 时间复杂度 最佳 \(O(n\log n)\) 最坏 \(O(n^2)\)
//快速排序 非稳定算法
#include
#include
using namespace std;

void qsort(int nums[], int l, int r){
    if(l>=r)return;

    //枢纽量 左指针 右指针
    int x = nums[l], i = l-1,j=r+1;

    while(ix);
        if(i

前缀和与差分

前缀和

//一维前缀和
#include
using namespace std;
const int N = 100010;

int num[N], pre[N];

//询问区间和
inline int q(int i,int j){
    return pre[j]-pre[i-1];
}

int main(){
    
    ios::sync_with_stdio(false);//提高cin的速率,无法使用scanf
    // 数据规模>=10^6 推荐使用scanf
    
    int n;
    cin>>n;
    //前缀和的原数组建议从1开始编号
    for(int i=1;i>num[i];
    for(int i=1;i
//二维前缀和
#include
using namespace std;

const int N = 1010;

//Sij代表x[1,i]y[1,j]矩阵内所有元素之和
int s[N][N];
int num[N][N];

//查询x[x1,x2]y[y1,y2]矩阵内和
inline int q(int x1,int y1,int x2,int y2){
    return s[x2][y2]+s[x1-1][y1-1]-s[x2][y1-1]-s[x1-1][y2];
}

int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>num[i][j];
    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]+num[i][j];
    
    return 0;
}

差分

//一维差分

//对于序列{a1,a2,...,an}
//构造序列{b1,b2,...,bn},使得ai = b1+b2+...+bi

//b1 = a1
//b2 = a2 - a1
//bi = ai - a(i-1)

//{b}称为{a}的差分

//用处:
// [l,r]+c O(n)
// ---> O(1)


//差分数组只有插入操作[l,r,c]
#include
using namespace std;
const int N = 100010;
int s[N];
//差分数组
int p[N];

void insert(int l, int r, int c){
    p[l] += c;
    p[r] -= c;
}
int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)cin>>s[i];
    //差分数组初始化
    for(int i=1;i<=n;i++)insert(i,i,s[i]);

    return 0;
}
//二维差分

#include
using namespace std;
const int N = 1010;
//p是差分数组
int p[N][N],s[N][N];

void insert(int x1, int y1, int x2, int y2, int c){
    p[x1][y1]+=c,p[x2+1][y1]-=c,p[x1][y2+1]-=c,p[x2+1][y2+1]+=c;
}

int main(){
    int n,m,k;
    for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>s[i][j];
    for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)insert(i,j,i,j,s[i][j]);
    
    return 0;
}

双指针

for (int i = 0, j = 0; i < n; i ++ )
{
    while (j < i && check(i, j)) j ++ ;

    // 具体问题的逻辑
}
常见问题分类:
    (1) 对于一个序列,用两个指针维护一段区间
    (2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作

二分

  • 时间复杂度 \(O(\log n)\)

整数二分

判左二分 [a,b] -> [a,mid] [mid+1 , b]

int l = 0, r = len;
while(l

判右二分 [a,b] -> [a,mid-1] [mid , b]

int l = 0, r = len;
while(l

浮点数二分

double l = 0, r = len;
while(fabs(l-r)>eps/*精度 取题目要求的精度小两个数量级*/){
    double mid = (l+r)/2;
    if(/**/)l = mid;
    else r = mid;
}
return l;

其他基础算法

离散化

vector 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
}

区间合并

// 将所有存在交集的区间合并
void merge(vector &segs)
{
    vector 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;
        }
        else ed = max(ed, seg.second);

    if (st != -2e9) res.push_back({st, ed});

    segs = res;
}

图论基础算法

单源最短路

Dijistra算法

  • 时间复杂度 \(O(nm)\) $m:边的数量 $ \(n : 点的数量\)
#include
#include
using namespace std;

//最大节点数
const int N = 510;

//邻接矩阵 d[a][b] a->b的权重
//最短距离 d[i] 1->path->i的权值和 d[1]=0
//st[i] 点i是否已经求得最短距离
int d[N][N],dist[N],st[N];

int main(){
    //全部初始化为极大值
    memset(d,0x3f,sizeof d);
    memset(dist,0x3f,sizeof dist);
    
    //点数n,边数m
    int n,m;
    cin>>n>>m;
    while(m--){
        // a->b 权重c
        int a,b,c;
        cin>>a>>b>>c;
        
        //可能存在重边,取最小权重即可
        d[a][b] = min(d[a][b],c);
    }
    
    //DJ
    dist[1]=0;
    for(int i=0;ipath->t距离最小的t
        int t = -1;
        for(int j=1;j<=n;j++){
            if(!st[j] && (t == -1 || dist[j]path->n仍然为初始化的极大值,则说明1到n无法到达
    cout<< (dist[n]==0x3f3f3f3f?-1:dist[n])<

堆优化的Dijistra算法

  • 时间复杂度 \(O(m\log n)\) $m:边的数量 $ \(n : 点的数量\)
#include
#include
#include
#include
using namespace std;
const int N = 150010;
typedef pair PII;//first 路径长 second 终点节点的编号

//使用邻接表储存稀疏图
int h[N],e[N],en[N],eg[N],idx;

int n,m;
int dist[N],st[N];
void add(int a, int b, int v){
    e[idx] = b;en[idx] = h[a];h[a] = idx;eg[idx++] = v;
}

int main(){
    //最小堆
    priority_queue,greater>heap;
    
    memset(h,-1,sizeof h);
    memset(dist,0x3f,sizeof dist);
    
    //存图
    cin>>n>>m;
    for(int i=0;i>a>>b>>c;
        add(a,b,c);
    }
    
    //堆优化的DJ算法
    dist[1] = 0;
    heap.push({0,1});
    while(heap.size()){
        //弹取最小路径
        auto m = heap.top();
        heap.pop();
        
        //i 终点节点编号 j 路径长
        int i = m.second, j = m.first;
        
        //如果i已经确定最短路径,则跳过
        if(st[i])continue;
        else st[i]=true;
        
        for(int t = h[i];t!=-1;t = en[t]){
            //e[t] 下一节点编号
            if(dist[e[t]] > j + eg[t]){
                dist[e[t]] = j+eg[t];
                heap.push({dist[e[t]],e[t]});
            }
        }
    }
    
    cout<<(dist[n]==0x3f3f3f3f?-1:dist[n])<

Bellman-Ford算法

  • 时间复杂度 \(O(nm)\) $m:边的数量 $ \(n : 点的数量\)
#include
#include
const int N = 510,M = 100010,INF = 0x3f3f3f3f;
struct edge{
    int a,b,w;
}edges[M];
int dist[N],backup[N];
int n,m;

//计算含边数不超过k条的最短路
void bf(int k){
    memset(dist,0x3f,sizeof dist);
    for(int i=0;i>n>>m;
    for(int i=0;i>a>>b>>v;
        edges[i] = {a,b,v};
    }
    
    bf(n-1);
    
    if(dist[n]>INF/2)cout<<"No"<

Bellman-Ford判负权回路

  • 时间复杂度 \(O(nm)\) $m:边的数量 $ \(n : 点的数量\)
#include
#include
using namespace std;

const int N = 2020, M = 10010, MAX = 0x3f3f3f3f;
struct ed{int a,b,w;} eds[M];
int dist[N], bp[N];
int main(){
    int n,m;
    scanf("%d%d", &n, &m);
    for(int i = 0;idist[a]+w){neg = true;break;}
    }
    if(neg)cout<<"Yes"<

SPFA算法

  • 时间复杂度 : 最佳 \(O(m)\) 最坏 \(O(nm)\) $m:边的数量 $ \(n : 点的数量\)
#include
#include
using namespace std;
const int N = 100010;
int n,m;

//邻接表储存稠密图
int h[N],e[N],ne[N],w[N],idx=0;
void add(int a, int b, int v){
    e[idx] = b,ne[idx] =h[a],w[idx] = v, h[a] = idx++;
}

int dist[N];
//队列
int q[N],ff=0,tt=-1;
bool st[N];

int main(){
    memset(h,-1,sizeof h);
    memset(dist,0x3f,sizeof dist);
    
    //读图
    scanf("%d%d",&n,&m);
    while(m--){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    
    //将起点纳入队列
    st[1] = true;
    q[++tt] = 1;
    dist[1] = 0;
    
    //BFS
    while(ff<=tt){
        int t = q[ff++];
        st[t] = false;
        
        //搜索所有旁支
        for(int i = h[t];i!=-1;i = ne[i]){
            int j = e[i];
            
            //可更新,则更新,如果也没在队列,则加入队列
            if(dist[j]>dist[t]+w[i]){
                dist[j] = dist[t]+w[i];
                if(!st[j]){
                    q[++tt] = j;
                    st[j] = true;
                }
            }
        }
    }
    
    //dist[i]为初始极大值时,说明不连通
    if(dist[n]==0x3f3f3f3f)puts("impossible");
    else cout<

SPFA判负权回路

  • 时间复杂度 : 最佳 \(O(m)\) 最坏 \(O(nm)\) $m:边的数量 $ \(n : 点的数量\)
#include
#include
#include
using namespace std;
const int N = 2020, M = 10010;
int n,m;
int h[N],e[M],ne[M],w[M],idx=0;
void add(int a, int b, int v){
    e[idx] = b,ne[idx] = h[a],w[idx] = v,h[a] = idx++;
}

int dist[N],st[N];
//储存1到i的路径边数
int cnt[N];
int main(){
    memset(h,-1,sizeof h);
    //注意 dist初始化为0
    //memset(dist,0x3f,sizeof dist);
    
    cin>>n>>m;
    while(m--){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    
    queue q;
    //由于有可能不会遍历完所有点,所以将所有点都纳入队列;
    for(int i=1;i<=n;i++){
        st[i]=true;
        q.push(i);
    }
    
    while(q.size()){
        int t = q.front();
        q.pop();
        st[t] = false;
        
        for(int i=h[t];i!=-1;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;
                //含有n的节点的图的最短路路径最大为n-1
                //存在负权回路是会超过这个值
                if(cnt[j]>=n){
                    cout<<"Yes"<

多源最短路

Floyd算法

  • 时间复杂度 \(O(n^3)\) \(n : 点的数量\)
#include
using namespace std;
const int N = 210,M = 20020,INF = 1e9;

//d [i][j] i到j的最短路径
int d[N][N];
int n,m,k;
int main(){
    scanf("%d%d%d",&n,&m,&k);
    
    for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){
        if(i == j)d[i][i] = 0;
        else d[i][j]=INF;
    }
    while(m--){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        d[a][b] = min(d[a][b],c);
    }
    
    //Floyd
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                d[i][j] = min(d[i][j],d[i][k]+d[k][j]);
            }
        }
    }
    //end
    
    while(k--){
        int a,b;
        scanf("%d%d",&a,&b);
        if(d[a][b]

最小生成树

Prim算法

#include
#include
using namespace std;
const int N = 550, INF = 0x3f3f3f3f;
int n,m;
int d[N][N],dist[N];
bool st[N];

int prim(){
    int ans = 0;
    for(int i = 0;idist[j]))t = j;
        }
        st[t] = true;
        if(i && dist[t]==INF)return INF;
        if(i) ans += dist[t];
        
        for(int j = 1;j<=n;j++)dist[j] = min(dist[j],d[t][j]);
        
    }
    return ans;
}

int main(){

    cin>>n>>m;
    memset(d,INF,sizeof d);
    memset(dist,INF,sizeof dist);
    while(m--){
        int a,b,w;
        cin>>a>>b>>w;
        d[a][b] = d[b][a] = min(d[a][b], w);
    }
    int ans = prim();
    if(ans != INF){
        cout<

Krustal算法

#include
#include
using namespace std;
const int N = 100010,M = 200020;
int s[N];
int root(int x){
    if(x!=s[x])s[x] = root(s[x]);
    return s[x];
}
int n,m;
struct edge{
    int a,b,w;
    bool operator<(const edge& W)const{
        return w>n>>m;
    for(int i=0;i>ed[i].a>>ed[i].b>>ed[i].w;
    }
    sort(ed,ed+m);
    
    int res = 0, cnt =0;
    for(int i=1;i<=n;i++)s[i]=i;
    
    for(int i=0;i

二分图染色与匹配

判断二分图

#include
using namespace std;
const int N = 100010,M = 200020;

int h[N],e[M],ne[M],w[M],idx = 0;
void add(int a, int b){
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
int n,m;
int clr[N];
bool dfs(int u, int c){
    clr[u] = c;
    for(int i=h[u];i!=-1;i=ne[i]){
        int j = e[i];
        if(!clr[j]){
            if(!dfs(j,3-c))return false;
        }
        else if(clr[j] == c)return false;
    }
    return true;
}
#include
int main(){
    memset(h,-1,sizeof h);
    int n,m;
    cin>>n>>m;
    while(m--){
        int a,b;
        cin>>a>>b;
        add(a,b);add(b,a);
    }
    
    bool flg = true;
    for(int i =1;i<=n;i++){
        if(!clr[i]){
            if(!dfs(i,1)){
                flg = false;
                break;
            }
        }
    }
    if(flg)cout<<"Yes"<

匈牙利算法求最大匹配

#include
#include
using std::memset;
using std::cin;
using std::cout;
using std::endl;
const int N = 510,M = 100010;
int h[N],e[M],ne[M],idx=0;
int match[N];
bool st[N];
void add(int a, int b){
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}

bool pair(int x){
    for(int i=h[x];i!=-1;i = ne[i]){
        int j = e[i];
        if(!st[j]){
            st[j] = true;
            if(match[j] == 0 || pair(match[j])){
                match[j] = x;
                return true;
            }
        }
    }
    return false;
}

int main(){
    memset(h,-1,sizeof h);
    int n1,n2,m;
    cin>>n1>>n2>>m;
    while(m--){
        int a,b;
        cin>>a>>b;
        add(a,b);
    }
    
    int res = 0;
    for(int i=1;i<=n1;i++){
        memset(st,false,sizeof st);
        if(pair(i))res++;
    }
    
    cout<

字符串基础算法

字符串匹配

KMP

  • 时间复杂度 \(O(m)\) \(m : 文本串长度\)
#include
using namespace std;

const int N=10010, M = 1000010;
//约定字符串下标从1开始
char s[M],p[N];

//next数组
int ne[N];
int m,n;

int main(){
    cin>>m>>p+1>>n>>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++;
        //匹配结束,模式串整个跳转 j = ne[j]
        if(j == n){
            printf("%d ", i-n+1);
            j = ne[j];
        }
    }
    return 0;
}

字符串哈希

判断相等子串

  • 时间复杂度 \(O(q)\) \(q:查询的数量\)
#include
using namespace std;
const int N = 100010, P= 13331; //p取131 或 13331都可
// 关键: 实现了自动模2^64
typedef unsigned long long ULL;

// p[i] p^i h[i] [0,i]的子串的哈希值
ULL p[N], h[N];

//获取子串的哈希值
ULL gH(int i ,int j){
    return h[i-1]*p[j-i+1] - h[j];
}
int main(){
    int n,m;
    cin>>n>>m;
    string s;
    cin>>s;
    p[0] = 1;
    for(int i = 1;i<=s.size();i++){
        p[i] = P*p[i-1];
        h[i] = h[i-1]*P+s[i-1];
    }
    while(m--){
        int i,j,k,l;
        cin>>i>>j>>k>>l;
        if(gH(i,j)==gH(k,l))cout<<"Yes"<

数论基础算法

质数

判断质数

  • 时间复杂度 \(O(\sqrt n)\)
bool is_prime(int x){
    for(int i=2;i<=x/i;i++){
        if(x%i==0)return false;
    }
    return true;
}

分解质因数

  • 时间复杂度 最佳\(O(\log n)\) 最坏 \(O(\sqrt n)\)
void divide(int x){
    for(int i=2;i<= x/i;i++){
        if(x%i == 0){
            int t = 0;
            while(x%i == 0){
                x/=i;
                ++s;
            }
            //质因数 次数
            printf("%d %d\n",i,t);
        }
    }
    if(x > 1)printf("%d %d\n",x , 1);
}

筛质数

  • 时间复杂度 \(O(n \log \log n)\)
bool p[N];
int prime[N],cnt;
int s(int n){
    for(int i=2;i<=n;i++){
        if(!p[i]){
            prime[cnt++] = i;
            for(int j=i+i; j <= n;j+=i)p[j] = true;
        }
    }
}
  • 时间复杂度 \(O(n\log \log n)\)
bool p[N];
int prime[N],cnt;
bool ai(int n){
    for(int i=2;i <= n;i++){
        if(!p[i]) prime[cnt++] = i;
        for(int j =0 ; prime[j] <= n/i; j++){
            p[prime[j]*i] = true;
            //i%prime[j]==0  -> prime[j]一定是i的最小质因子
            if(i % prime[j] == 0)break;
        }
    }
}

约数

所有约数

  • 时间复杂度 \(O(\sqrt{n}+\log{n}\log{\log{n}})\)
vector diver(int x){
    vector ans;
    for(int i=1;i<= x/i;i++){
        if(x%i == 0){
            ans.push_back(i);
            if(i != x/i)ans.push_back(x/i);
        }
    }
    sort(ans.begin(), ans.end());
    return ans;
}

约数个数

  • 时间复杂度 最坏 \(O(\sqrt n)\) 最佳 \(O(\log n)\)
int diver_cnt(int x){
    int res = 1;
    for(int i=2;i<=x;i++){
        if(x%i == 0){
            int alpha=0;
            while(x%i ==0){
                x/=i;
                alpha++;
            }
            res*= (alpha+1);
        }
    }
    if(x>1)res*=2;
    return res;
}

约数之和

typedef unsigned long long ULL;
const int mod = 100007;
ULL diver_sum(int x){
    ULL res = 1;
    for(int i=2;i<=x;i++){
        if(x%i == 0){
            int alpha=0;
            while(x%i ==0){
                x/=i;
                alpha++;
            }
            //秦九昭算法 1+q+..+q^n = 1+q(1+q(...))
            ULL t = 1
            for(int j =0;j1)res = res*(x+1)%mod;
    return res;
}

最大公约数与最小公倍数

// 最大公约数
int gcd(int a, int b){
    return b? gcd(b,a%b):a;
}

//最小公倍数
int lcm(int a, int b){
    return a*b/gcd(a,b);
}

快速幂

  • 时间复杂度 \(O(\log k)\) \(k:幂\)
typedef long long LL;
// a^k % p
LL quick_pow(int a, int k, int p){
    int res = 1;
    while(k){
        if(k & 1) res = (LL)res*a % p;
        k>>=1;
        a = (LL) a*a % p;
    }
    return res;
}

快速积

  • 时间复杂度 \(O(\log n)\) \(n: 乘积项的任意一个\)
typedef unsigned long long ULL;
// a*b % p
ULL quick_add(ULL a, ULL b, ULL p){
    ULL res = 0;
    while(b){
        if(b & 1)r = (r + a)%p;
        b >>= 1;
        a = a*2 % p;
    }
    return res;
}

求组合数算法

递推公式法

  • 时间复杂度 \(O(n^2)\)
const int mod = 1e9+7;
int C[N][N];//C[a][b]
void init(){
    for(int i=0;i

阶乘公式法

  • 时间复杂度 \(O(n\log n)\)
typedef long long LL;
const int mod = 1e9+7;//模是质数
int fact[N],infact[N];

int quick_pow(int a,int k, int p);
void init(){
    fact[0] = infact[0] = 1;
    for(int i=1;i

经典动态规划模型

序列模型

最长上升子序列

  • 时间复杂度 \(O(n^2)\)
  • 状态定义: \(f(i) : 以第i个数结尾的最长上升子序列的长度\)
  • 状态转移: \(f(i) = \max_{j:1\to i-1} : f(j)+1\)
#include
using namespace std;
const int N = 1010;

// p 序列 f[i] 以第i个数为结尾的最长上升子序列的长度
int p[N],f[N];

int main(){
    int n;
    cin>>n;
    for(int i =1;i<=n;i++){
        cin>>p[i];
    }
    //初始化
    for(int i = 1;i<=n;i++)f[i] = 1;
    for(int i = 2;i <= n;i++){
        for(int j = 1;j
  • 时间复杂度 \(O(n\log n)\)
#include
#include
using namespace std;
const int N = 100010;
int p[N],f[N];
int main(){
    int n;
    cin>>n;
    for(int i = 0;i>p[i];
    int cnt = 1;
    f[0] = p[0];
    for(int i = 1;i

最小覆盖问题

结论:最长上升子序列的最小覆盖数等于最长不上升子序列的长度

最长公共子序列

  • 状态定义 \(f(i,j) : 公共序列的末尾元素是A序列的第i个数和B序列的第j个数时的最长长度\)
#include
using namespace std;
const int N = 1010;
char A[N],B[N];
int f[N][N];
int main(){
    int n,m;
    cin>>n>>m;
    for(int i = 1;i<=n;i++)cin>>A[i];
    for(int j = 1;j<=m;j++)cin>>B[j];
    
    // 分类 : 序列末尾是A[i]\是B[j]\是A[i]和B[j]\都不是
    for(int i = 1;i<=n;i++)
        for(int j = 1;j<=m;j++)
            if(A[i] == B[j])f[i][j] = f[i-1][j-1]+1;
            else{
                f[i][j] = max(max(f[i-1][j],f[i-1][j-1]),f[i][j-1]);
            }
    cout<

背包模型

01背包

  • 时间复杂度 \(O(nv)\) \(n:物品数量\) $ v: 背包体积$
#include
using namespace std;
const int N = 1010;
int dp[N],w[N],v[N];

int slove(int n, int mv){
    for(int i = 1; i<= n; i++){
        for(int j = mv;j>=v[i];j--){
            dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
        }
    }
    return dp[mv];
}

int main(){
    int n,mv;
    cin>>n>>mv;
    for(int i = 1;i<=n;i++)cin>>v[i]>>w[i];
    cout<

完全背包

#include
using namespace std;
const int N = 1010;

int dp[N],w[N],v[N];

int main(){
    int n,mv;
    cin>>n>>mv;
    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<=mv;j++)
            dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
    
    cout<

多重背包

#include
using namespace std;
const int N = 100010;
int dp[N],w[N],v[N];

int main(){
    int n,mv,idx=0;
    cin>>n>>mv;
    for(int i = 1;i<=n;i++){
        int a,b,s;
        cin>>a>>b>>s;
        int d = 1;
        while(s>=d){
            idx++;
            w[idx] = b*d;
            v[idx] = a*d;
            s-=d;
            d*=2;
        }
        if(s>0){
            idx++;
            w[idx] = b*s;
            v[idx] = a*s;
        }
    }
    
    for(int i = 1;i<=idx;i++)for(int j = mv;j>=v[i];j--)dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
    cout<

单调队列优化

#include 

using namespace std;

const int N = 1010, M = 20010;

int n, m;
int v[N], w[N], s[N];
int f[2][M];
int q[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 r = 0; r < v[i]; ++ r)
        {
            int hh = 0, tt = -1;
            for (int j = r; j <= m; j += v[i])
            {
                while (hh <= tt && j - q[hh] > s[i] * v[i]) hh ++ ;
                while (hh <= tt && f[(i - 1) & 1][q[tt]] + (j - q[tt]) / v[i] * w[i] <= f[(i - 1) & 1][j]) -- tt;
                q[ ++ tt] = j;
                f[i & 1][j] = f[(i - 1) & 1][q[hh]] + (j - q[hh]) / v[i] * w[i];
            }
        }
    }
    cout << f[n & 1][m] << endl;
    return 0;
}

分组背包

#include
using namespace std;
const int N = 150;
int dp[N],v[N][N],w[N][N],s[N];

int slove(int n, int mv){
    for(int i = 1;i<=n;i++)
        for(int j = mv; j>=0; j--)
            for(int k = 0;k<=s[i];k++)
                if(j>=v[i][k])dp[j] = max(dp[j-v[i][k]]+w[i][k], dp[j]);
    return dp[mv];
}
int main(){
    int n,mv;
    cin>>n>>mv;
    for(int i =1;i<=n;i++){
        cin>>s[i];
        for(int j = 1;j<=s[i];j++){
            cin>>v[i][j]>>w[i][j];
        }
    }
    slove(n,mv);
    cout<

数位统计

  • 时间复杂度 \(G函数的时间复杂度\)
[a,b]间满足性质K的数的个数 -> G(x)是 [s,x]间满足性质K的数的个数 [a,b] = G(b) - G(a-1)
cnt G( x ) :
	将x拆分为每一位
	
	统计性质

	返回个数
	return cnt