一些简单的入门算法模版

DFS

递归实现指数型枚举

void dfs(int u){
      if(u>=n){
           for(auto it:vec)  cout<<it<<" ";
           cout<<endl;
           return ;
      }
      for(int i=1;i<=n;i++)
         if(!st[i]){
             st[i]=true;     //选
             vec.push_back(i);
             dfs(u+1);
             st[i]=false;    //不选
             vec.pop_back();
         }
}

递归实现排列型枚举

//求1~n的全排列,共有2的n次方种情况
void dfs(int u){
     if(u>n){      //已经选了n个数
         for(auto it:vec)  cout<<it<<" ";
         cout<<endl;
         return ;
     }
     for(int i=1;i<=n;i++)  //排列与顺序无关,下标从1开始
       if(!st[i]){   //选目前没选的数
           st[i]=true;      
           vec.push_back(i);
           dfs(u+1);
           st[i]=false;    
           vec.pop_back();
       }
}

BFS

迷宫问题

int n;
int g[N][N];
PII q[M];
PII pre[N][N];
int d[N][N];
void bfs(int sx,int sy){
    int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
    memset(d,-1,sizeof d);   //初始化
    int hh=0,tt=0;
    q[0]={sx,sy};
    d[sx][sy]=0;
    while(hh<=tt){
        PII t=q[hh++];
        for(int i=0;i<4;i++){
            int a=t.x+dx[i],b=t.y+dy[i];
            if(a>=0&&a<n&&b>=0&&b<n&&d[a][b]==-1&&g[a][b]==0){
                d[a][b]=d[t.x][t.y]+1;
                pre[a][b]=t;    //记录上一个节点
                q[++tt]={a,b};
            }
        }
    }
}
//--------------------------------------------------------------
//若是求路径,可以bfs终点然后从始点遍历pre数组打印路径
  PII end(0,0);   //始点
        while(true){
            cout<<end.x<<" "<<end.y<<endl;
            if(end.x==n-1&&end.y==n-1)  break;   //等打印到终点break
            end=pre[end.x][end.y];    
        }

树的重心

//将重心删除后,剩余各个连通块中点数的最大值。
int ans=N;    //记录答案
int n;       //树的节点
bool st[N];
 
//我们需要记录删除重心后剩下联通块的最大值,剩下的联通块有当前节点的子节点和上面父节点组成的部分,所以比较的时候先比较子节点的数量,因此dfs子节点需要返回子节点的数量,然后总结点减去子节点数量+1为上面部分的数量比较求最大值,然后更新答案
int dfs(int u){   // 返回子节点的数量
    st[u]=true;
    int sum=1;       
    int res=0;    
    for(int i=h[u];i!=-1;i=ne[i]){
        int j=e[i];
        if(!st[j]){
         int u=dfs(j);    //子节点的数量  
         sum+=u;      
         res=max(res,u);
        }
    }
res=max(res,n-sum); 
ans=min(ans,res);
    
    return sum;
}

拓扑排序

int d[N];   //记录每个点的入度
void topsort(){
    int q[N];
    int hh=0,tt=-1;
    for(int i=1;i<=n;i++)  if(!d[i])   q[++tt]=i;   //将入度为零的点加入队列
    while(hh<=tt){
        auto t=q[hh++];
        for(int i=h[t];i!=-1;i=ne[i]){
            int j=e[i];
            d[j]--;          //t所连的点入度减一
            if(!d[j])  q[++tt]=j;   //当点的入度为0时加入到队列里
        }
    }
    if(tt==n-1){    //没有闭环最后队列会有n个数
         for(int i=0;i<=tt;i++)   cout<<q[i]<<" ";   
         
    }
    else cout<<-1<<endl;
}

朴素版Dijkstra

const int N=1010;
int g[N][N];
int dist[N];
int n,m;
bool st[N];

void dijkstra(){
    memset(dist,0x3f,sizeof dist);  
    dist[1]=0;
      for(int i=0;i<n;i++){       //共有n个点循环n次每次找到最近的点
          int t=-1;
          for(int j=1;j<=n;j++)
             if(!st[j]&&(t==-1||dist[j]<dist[t]))
               t=j;
            
       st[t]=true;
       for(int j=1;j<=n;j++)     //找到最近的点进行更新
          dist[j]=min(dist[j],dist[t]+g[t][j]);  
      }
      if(dist[n]==0x3f3f3f3f)  cout<<-1<<endl;
      else cout<<dist[n]<<endl;
}

堆优化Dijkstra


int dist[N];
bool st[N];
int dijkstra(){
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    priority_queue<PII,vector<PII>,greater<PII>>q;
    q.push({0,1});
    while(q.size()){
        PII t=q.top();
        q.pop();
        int ver=t.second;
        if(st[ver]) continue; //用改节点已经更新过最短路了continue
        st[ver]=true;
        for(int i=h[ver];i!=-1;i=ne[i]){
            int j=e[i];
            if(dist[j]>dist[ver]+w[i]){
                dist[j]=dist[ver]+w[i];
                q.push({dist[j],j});
            }
        }
    }
    if(dist[n]==0x3f3f3f3f)  return -1;
    else return dist[n];
}

spfa

int dist[N];
bool st[N];

int spfa(){
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    st[1]=true;
    queue<PII>q;
    q.push({0,1});
    while(q.size()){
        PII t=q.front();
        q.pop();
        int ver=t.second;
        st[ver]=false;
        for(int i=h[ver];i!=-1;i=ne[i]){
            int j=e[i];
            if(dist[j]>dist[ver]+w[i]){
                dist[j]=dist[ver]+w[i];
                if(!st[j]){
                    st[j]=true;
                    q.push({dist[j],j});
                }
            }
        }
    }
    return dist[n];
}

Floyd

int g[N][N];

void floyd(){
    for(int k=1;k<=n;k++)   //每个点都作为中转站进行更新
      for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
          g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
}

Kruskal求最小生成树

//并查集来求最小生成树

int n,m;
int p[N];
int res=0,cnt=0;

struct E{
    int a,b,w;           //a->b   边权为w
    bool operator<(const E&rhs){
        return this->w<rhs.w;
    }
}e[M*2];    //节点

int find(int x){
    if(p[x]!=x)  p[x]=find(p[x]);
    return p[x];
}

void kruskal(){
    for(int i=0;i<m;i++){   //遍历出所有的边
        int a=e[i].a,b=e[i].b,w=e[i].w;
        if(find(a)!=find(b)){ //a和b不在一个集合加入进去
            res+=w;
            p[find(a)]=find(b);
            cnt++;
        }
    }
}

//--------------------- 
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++)   p[i]=i;
    for(int i=0;i<m;i++){
        int u,v,w;
        cin>>u>>v>>w;
        e[i]={u,v,w};
    }
    sort(e,e+m);   //按边权w进行排序
    kruskal();
    if(cnt<n-1)   cout<<"impossible"<<endl;
    else cout<<res<<endl;
}

01背包

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

int main(){
    int n,m;
    cin>>n>>m;   
    for(int i=0;i<n;i++)   cin>>v[i]>>w[i];
    for(int i=0;i<n;i++)      //先物品在背包
       for(int j=m;j>=v[i];j--)   //01背包只有一个,保证每次更新不会被上次更新影响到背包从后遍历
         f[j]=max(f[j],f[j-v[i]]+w[i]);
         
         cout<<f[m]<<endl;
}

完全背包c

int n,m;
int v[N],w[N];
int f[N];

int main(){
    cin>>n>>m;
    for(int i=0;i<n;i++)  cin>>v[i]>>w[i];
    for(int i=0;i<n;i++)
     for(int j=v[i];j<=m;j++)  //防止数组越界背包从v[i]开始
       f[j]=max(f[j],f[j-v[i]]+w[i]);
       cout<<f[m];
}

多重背包

int n,m;
int f[N];

int main(){
  cin>>n>>m;
  for(int i=0;i<n;i++){
      int v,w,s;  //重量,价值,个数
      cin>>v>>w>>s;
      for(int k=1;k<=s;k*=2){   //每一组进行01背包,每组按二进制枚举
          for(int j=m;j>=k*v;j--)   
                f[j]=max(f[j],f[j-v*k]+w*k);
                       s-=k;    
      }
      if(s){           //不够二进制数目的新算一组
          for(int j=m;j>=s*v;j--)
       f[j]=max(f[j],f[j-v*s]+w*s);
      }
}
  cout<<f[m]<<endl;
}

分组背包

int f[N],v[N][N],w[N][N],s[N];  //重量,价值,组数 
int n,m;

int main(){
    cin>>n>>m;
    for(int i =0;i<n;i++){
        cin>>s[i];      //第一组有几个
        for(int j=0;j<s[i];j++)
        cin>>v[i][j]>>w[i][j];
    }                         
    for(int i=0;i<n;i++)   //物品只有一个01背包
      for(int j=m;j>=0;j--)
         for(int k=0;k<s[i];k++)
           if(j>=v[i][k])  f[j]=max(f[j],f[j-v[i][k]]+w[i][k]); //每一组中选一个价值最大的
           cout<<f[m]<<endl;
}

数字三角形

int a[N][N];
int n;
int dp[N][N];

int main(){
    cin>>n;
    for(int i=1;i<=n;i++)
     for(int j=1;j<=i;j++)
       cin>>a[i][j];
        
     for(int i=0;i<=n;i++)     //求最大,每个点初始化为最小值
     for(int j=0;j<=i+1;j++)
       dp[i][j]=-1e9;
       
     dp[1][1]=a[1][1];
     for(int i=2;i<=n;i++)
       for(int j=1;j<=i;j++)   //当前的点是由上面和斜上方转移过来的
         dp[i][j]=a[i][j]+max(dp[i-1][j-1],dp[i-1][j]);
        int res=-1e9;
    for(int i=1;i<=n;i++) res=max(res,dp[n][i]);  //枚举最后一行求最大值
    cout<<res<<endl;
       
}

最长上升最序列 I

//状态表示:f[i]表示从第一个数字开始算,以a[i]的最大的上升序列。(以a[i]结尾的所有上升序列中属性为最大值的那一个)
const int N=1010;
int n;
int a[N],f[N]; 
int res=0;
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)  cin>>a[i];
    
    for(int i=1;i<=n;i++){
        f[i]=1;    //初始长度为1
        for(int j=1;j<i;j++){   //遍历前面的点更新f[i]
            if(a[j]<a[i]){
                f[i]=max(f[i],f[j]+1);
            }
        }
        res=max(res,f[i]);
    }
    cout<<res<<endl;
}

最长上升子序列 II

//单调栈优化可解决1e5的数据,贪心思路,始终保持目前维护的子序列中的数越小越好,当加入的数要比序列中最后一个小的时候直接加入队列里,否则用它替换队列中第一个>=的数
vector<int>p;

int main(){
    int n,x;
    cin>>n;
    cin>>x;
    p.push_back(x);
    for(int i=1;i<n;i++){
        cin>>x;
        if(p.back()<x)  p.push_back(x); 
        else *lower_bound(p.begin(),p.end(),x)=x;
    }
    cout<<p.size()<<endl;
    
}

最长公共子序列

const int N=1010;
int n,m;
char a[N],b[N];
int f[N][N];

int main(){
    cin>>n>>m;
    cin>>a+1>>b+1;
    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(f[i-1][j],f[i][j-1]);
        }
}
cout<<f[n][m];
}

区间合并

const int N=310;
int n;
int s[N];
int f[N][N];
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)  cin>>s[i];
    for(int i=1;i<=n;i++)   s[i]+=s[i-1];
    
    for(int len=2;len<=n;len++)  //枚举长度
       for(int i=1;i+len-1<=n;i++){
           int l=i,r=i+len-1;
           f[l][r]=1e9;
         for(int k=l;k<=r;k++)  //枚举分割点
            f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
       }
       cout<<f[1][n]<<endl;
}

整数划分

//现在给定一个正整数 n,请你求出n共有多少种不同的划分方法。
const int N=1010,mod=1e9+7;
int dp[N];
int main(){
    int n;
    cin>>n;
    dp[0]=1;  //初始化  
    for(int i=1;i<=n;i++)   //可以看成是完全背包,用1~n的物品装满大小为n的背包
       for(int j=i;j<=n;j++)
          dp[j]=(dp[j]+dp[j-i])%mod;   //总方案数为选第i件物品和不选第i件物品的方案总和
           cout<<dp[n];
    
}

蒙德里安的梦想

//求把 N×M 的棋盘分割成若干个 1×2的长方形,有多少种方案。
//状态表示 f[i][j]: 前i-1列已经确定,且从第i-1列伸出的小方格在第i列的状态为j 的方案数。属性:个数。
const int N=12,M=1<<N;  
typedef long long ll;
ll f[N][M];
bool st[M];  //存预处理所有情况
int n,m;
int main(){
    while(cin>>n>>m,n||m){
        memset(f,0,sizeof f);
       for(int i=0;i<1<<n;i++){    //枚举一列中所有的状态,有2的n次方种
              int cnt=0;           //记录两个1之间的间隔
              st[i]=true;          //初始化为true
              for(int j=0;j<n;j++){    //枚举出当前列的状态的每一位
                  if(i>>j&1){       //如果是1则需要判断两个1之间的位置是不是偶数
                      if(cnt&1){
                       st[i]=false;
                      break;   
                      }
                  }
                  else cnt++;  //不是1则cnt++
              }
      if(cnt&1)  st[i]=false;  //出来后需要在判断一次        
       }
        
        f[0][0]=1;           //初始化
        for(int i=1;i<=m;i++)         //枚举行
           for(int j=0;j<1<<n;j++)    //当前行的状态
              for(int k=0;k<1<<n;k++)   //上一行的状态
                 if((j&k)==0&&st[j|k])  //如果能从上一状态转移过来则更新答案
                    f[i][j]+=f[i-1][k];
                    
                    cout<<f[m][0]<<endl;
    }
}

最短Hamilton路径

//状态表示:f[i][j];集合:所有从0走到j,走过的所有点的情况是i的所有路径
//状态转移方程:f[i][j]=min(f[i][j],f[i-(1<<j)][k]+w[k][j])

const int N=20,M=1<<N;

int n;
int w[N][N];
int f[M][N];

int main(){
    cin>>n;
    for(int i=0;i<n;i++)
      for(int j=0;j<n;j++)
        cin>>w[i][j];
    memset(f,0x3f,sizeof f);
    f[1][0]=0;
    for(int i=0;i<1<<n;i++)   //枚举所有状态
        for(int j=0;j<n;j++)  //第一个点
           if(i>>j&1)      //如果经过当前点的话
          for(int k=0;k<n;k++)  //第一个点的中转点
             if(i-(1<<j)>>k&1)  //若这个点存在则更新
                 f[i][j]=min(f[i][j],f[i-(1<<j)][k]+w[k][j]); 
                 
        cout<<f[(1<<n)-1][n-1]<<endl;         
}

没有上司的舞会

void dfs(int u){
    st[u]=true;
      f[u][1]=happy[u];   //去
      for(int i=h[u];i!=-1;i=ne[i]){
          int j=e[i];
          if(!st[j]){
               dfs(j);
          f[u][1]+=f[j][0];   //去的话孩子不去
          f[u][0]+=max(f[j][1],f[j][0]);  //不去的话孩子可去可不去  
          }
      }
}


滑雪

int dp(int x,int y){
    int &v=f[x][y];
    if(v!=-1)  return v; //若是求出来过则直接返回
    v=1;
   for(int i=0;i<4;i++){
       int xx=x+dx[i],yy=y+dy[i];
       if(xx>=1&&xx<=n&&yy>=1&&yy<=m&&h[x][y]>h[xx][yy]){
             v=max(v,dp(xx,yy));    
       }
   }
   return v;//加上当前点能滑的最远的距离
}

一个简单的整数问题I

//给定一个正整数数列 a1,a2,…,an,每一个数都在 0∼p−1之间。
//可以对这列数进行两种操作:
//添加操作:向序列后添加一个数,序列长度变成 n+1;
//询问操作:询问这个序列中最后 L个数中最大的数是多少。
//程序运行的最开始,整数序列为空。一共要对整数序列进行 m次操作。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>PII;
const int N=2e5+10;
int m,p;
struct node{
    int l,r;
    int v;
}tr[N*4];

void pushup(int u){
    tr[u].v=max(tr[u<<1].v,tr[u<<1|1].v);
}

void build(int u,int l,int r){
      tr[u]={l,r};
      if(l==r)  return ;
      int mid=l+r>>1;
      build(u<<1,l,mid);
      build(u<<1|1,mid+1,r);
}

int query(int u,int l,int r){
      if(tr[u].l>=l&&tr[u].r<=r)   return tr[u].v;
        int mid=tr[u].l+tr[u].r>>1;
        int v=0;
   if(l<=mid)  v=query(u<<1,l,r);
   if(r>mid)    v=max(v,query(u<<1|1,l,r));
   return v;
}


void modify(int u,int x,int v){
     if(tr[u].l==x&&tr[u].r==x)   tr[u].v=v;
       else{
           int mid=tr[u].l+tr[u].r>>1;
           if(x<=mid)   modify(u<<1,x,v);
              else   modify(u<<1|1,x,v);
              pushup(u);
       }
}

int main(){
    int n=0,last=0;
    cin>>m>>p;
    build(1,1,m);
  string op;
    while(m--){
        cin>>op;
        if(op[0]=='A'){
            int x;
            cin>>x;
            modify(1,n+1,((ll)last+x)%p);
            n++;
        }else{
            int L;
            cin>>L;
           last=query(1,n-L+1,n);
           cout<<last<<endl;
        }
    }
}

一个简单的整数问题II


//给定一个长度为 N的数列 A,以及 M条指令,每条指令可能是以下两种之一:

//C l r d,表示把 A[l],A[l+1],…,A[r]都加上 d
//Q l r,表示询问数列中第 l∼r个数的和。
//对于每个询问,输出一个整数表示答案。


#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

typedef long long LL;

const int N = 1e5 + 10;
int n, m;
int w[N];
struct Node
{
    int l, r;
    LL sum, add;
}tr[N * 4];

void pushup(int u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u)
{
    auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
    if (root.add)
    {
        //传递懒标记,更新子树
        left.add += root.add, left.sum += (LL) (left.r - left.l + 1) * root.add;
        right.add += root.add, right.sum += (LL) (right.r - right.l + 1) * root.add;
        //删除父结点懒标记
        root.add = 0;
    }
}
void build(int u, int l, int r)
{
    if (l == r) tr[u] = {l, r, w[l], 0};
    else
    {
        tr[u] = {l, r};
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}
void modify(int u, int l, int r, int v)
{
    if (l <= tr[u].l && tr[u].r <= r)
    {
        tr[u].sum += (tr[u].r - tr[u].l + 1) * v;
        tr[u].add += v;
    }
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r, v);
        if (r > mid) modify(u << 1 | 1, l, r, v);
        pushup(u);
    }
}
LL query(int u, int l, int r)
{
    if (l <= tr[u].l && tr[u].r <= r) return tr[u].sum;

    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    LL v = 0;
    if (l <= mid) v = query(u << 1, l, r);
    if (r > mid) v += query(u << 1 | 1, l, r);
    return v;
}
int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) scanf("%d", &w[i]);
    build(1, 1, n);

    char op[2];
    int l, r, t;
    while (m -- )
    {
        scanf("%s%d%d", op, &l, &r);
        if (*op == 'Q') printf("%lld\n", query(1, l, r));
        else
        {
            scanf("%d", &t);
            modify(1, l, r, t);
        }
    }
    return 0;
}

  • 10
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值