算法基础模板整理(动态规划篇)

背包问题

01背包问题

static const int N = 1010;
int dp[N][N], v[N], w[N], n, c;

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

    for(int i = 1; i <= n; i ++ )
        for(int j = 1; j <= c; j ++ ){
            dp[i][j] = dp[i - 1][j];
            if(j >= v[i]) dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
        }
    cout << dp[n][c];

    return 0;
}
int dp[N], v[N], w[N], n, c;

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

    for(int i = 1; i <= n; i ++ )
        for(int j = c; j >= v[i]; j -- )    //需要倒序 
            dp[j] = max(dp[j], dp[j - v[i]] + w[i]); //保证是从上一层的数据过来的
    //如果正序 对于某个容量j,小于j的容量的dp值已经被更新,此时已经不再是上一层的了
    cout << dp[c];

    return 0;
}

完全背包问题

static const int N = 1010;
int dp[N][N], v[N], w[N], n, c;

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

    for(int i = 1; i <= n; i ++ )
        for(int j = 0; j <= c; j ++ ){
            dp[i][j] = dp[i - 1][j];
            if(j >= v[i]) dp[i][j] = max(dp[i][j], dp[i][j - v[i]] + w[i]);
        }
    /*
    dp[i][j] =       max(dp[i - 1][j], dp[i - 1][j - v] + w, dp[i - 1][j - 2 * v] + 2 * w ... )
    dp[i][j - v] =   max(              dp[i - 1][j - v],     dp[i - 1][j - 2 * v] + w     ... )
    所以
    dp[i][j] =       max(dp[i - 1][j], dp[i][j - v] + w)  
    */
    cout << dp[n][c];

    return 0;
}
//一维数组版本
int dp[N], v[N], w[N], n, c;

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

    cout << dp[c];

    return 0;
}

分组背包问题

static const int N = 110;
int dp[N], v[N], w[N];
int n, c;

int main(){
    cin >> n >> c;
    for(int i = 1; i <= n; i ++ ){
        int cnt; cin >> cnt;
        for(int j = 1; j <= cnt; j ++ ) cin >> v[j] >> w[j];
        for(int j = c; j >= 0; j -- )
            for(int k = 1; k <= cnt; k ++ )      //对于每个容量 每组只能选择一个 所以外循环为容量 内循环为物品
                if(j >= v[k]) dp[j] = max(dp[j], dp[j - v[k]] + w[k]);
    }

    cout << dp[c];

    return 0;
}

多重背包

const int N = 110;
int dp[N], n, c;

int main(){
    cin >> n >> c;
    int v, w, s;
    while(n -- ){
        cin >> v >> w >> s;
        for(int j = c; j >= 0; j -- )
            for(int k = 0; k <= s && k * v <= j; k ++ )
                dp[j] = max(dp[j], dp[j - k * v] + k * w);
    }
    cout << dp[c];
}

二维费用背包问题

const int N = 110;
int dp[N], n, c;

int main(){
    cin >> n >> c;
    int v, w, s;
    while(n -- ){
        cin >> v >> w >> s;
        for(int j = c; j >= 0; j -- )
            for(int k = 0; k <= s && k * v <= j; k ++ )
                dp[j] = max(dp[j], dp[j - k * v] + k * w);
    }
    cout << dp[c];
}

混合背包问题

#include<iostream>
using namespace std;

const int N = 1010;
int n, c, v, w, s;
int dp[N];

int main(){
    cin >> n >> c;
    while(n -- ){
        cin >> v >> w >> s;
        if(!s){
            for(int j = v; j <= c; j ++ )
                dp[j] = max(dp[j], dp[j - v] + w);
        }else if(s){
            if(s == -1) s = 1;
            for(int k = 1; k <= s; k <<= 1){
                for(int j = c; j >= k * v; j -- )
                    dp[j] = max(dp[j], dp[j - k * v] + k * w);
                s -= k;
            }
            if(s){
                for(int j = c; j >= s * v; j -- )
                    dp[j] = max(dp[j], dp[j - s * v] + s * w);
            }
        }
    }

    cout << dp[c] << endl;

    return 0;
}


数字三角形模型

for(int j = 0; j < n; j++)
    dp[n - 1][j] = a[n - 1][j];
for(int i = n - 2; i >= 0; i--)
    for(int j = 0; j < n; j++)
        dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]) + a[i][j];

cout << dp[0][0];


子序列

最长上升子序列模型

int ans = 0;
for(int j = 1; j <= n; j++){
    dp[j] = 1;
    for(int i = 1; i < j; i++){
        if(a[i] < a[j])
            dp[j] = max(dp[j], dp[i] + 1);
    }
    ans = max(ans, dp[j]);
}
cout << ans;

最长公共子序列模型

for(int i = 1; i <= n; i ++ )
    for(int j = 1; j <= m; j ++ )
        if(s[i] == t[j]) dp[i][j] = dp[i - 1][j - 1] + 1;
        else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
cout << dp[n][m];

最长公共上升子序列模型

for(int i = 1; i <= n; i ++ ){
    int maxv = 1;
    for(int j = 1; j <= n; j ++ ){   //枚举结尾
        dp[i][j] = dp[i - 1][j];
        if(a[i] == b[j]) dp[i][j] = max(dp[i][j], maxv);
        if(a[i] > b[j]) maxv = max(maxv, dp[i - 1][j] + 1);
    }
}

int res = 0;
for(int i = 1; i <= n; i ++ ) res = max(res, dp[n][i]);
cout << res;

子序列匹配个数模型

for(int i = 0; i <= m; i ++ ) dp[i][n] = 1;
        for(int i = m - 1; i >= 0; i -- )
            for(int j = n - 1; j >= 0; j -- )
                if(s[i] == t[j]) dp[i][j] = (dp[i + 1][j + 1] + dp[i + 1][j]) % mod;
                else dp[i][j] = dp[i + 1][j] % mod;
        
        cout << dp[0][0] % mod << endl;


最大子段和模型

cin >> n;
        for(int i = 1; i <= n; i ++ ) cin >> a[i];

        sum = 0;
        for(int i = 1; i <= n; i ++ )   //最大子段和模型
            sum = max(0, sum) + a[i], dp1[i] = max(dp1[i - 1], sum);

        sum = 0;
        for(int i = n; i; i -- ) 
            sum = max(0, sum) + a[i], dp2[i] = max(dp2[i + 1], sum);


区间DP

石子合并模型

cin >> n;
for(int i = 1; i <= n; i++) cin >> s[i], s[i] += s[i - 1];
for(int len = 2; len <= n; len ++ ){
    for(int i = 1; i + len - 1 <= n; i ++ ){
        int j = i + len - 1;
        dp[i][j] = INT_MAX;
        for(int k = i; k < j; k ++ )
            dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + s[j] - s[i - 1]);
    }
}
cout << dp[1][n];

环形石子合并模型

cin >> n;
for(int i = 1; i <= n; i ++ ){
    cin >> a[i];
    a[n + i] = a[i];    //化环为链
}

for(int i = 1; i <= 2 * n - 1; i ++ ) pre[i] = pre[i - 1] + a[i];

for(int len = 1; len <= n; len ++ ){
    for(int i = 1; i <= 2 * n - len - 1; i ++ ){
        int j = i + len;
        dp1[i][j] = inf;
        for(int k = i; k < j; k ++ ){
            dp1[i][j] = min(dp1[i][j], dp1[i][k] + dp1[k + 1][j] + pre[j] - pre[i - 1]);
            dp2[i][j] = max(dp2[i][j], dp2[i][k] + dp2[k + 1][j] + pre[j] - pre[i - 1]);
        }
    }
}   

int resmin = inf, resmax = 0;
for(int i = 1; i <= n; i ++ ){
    resmin = min(resmin, dp1[i][i + n - 1]);
    resmax = max(resmax, dp2[i][i + n - 1]);
}

cout << resmin << endl << resmax;

环形石子合并模型 + 最优分割点标记

for(int i = 1; i <= n; i ++ ){
    cin >> a[i];
    a[n + i] = a[i];
    s[i][i] = i, s[n + i][n + i] = n + i;
    dp[i][i] = dp[n + i][n + i] = 0;
}

for(int i = 1; i <= 2 * n; i ++ ) pre[i] = pre[i - 1] + a[i];

for(int len = 1; len <= n; len ++ ){
   for(int i = 1; i <= 2 * n - len; i ++ ){
      int j = i + len;
      for(int k = s[i][j - 1]; k <= s[i + 1][j]; k ++ ){
          if(dp[i][j] > dp[i][k] + dp[k + 1][j] + pre[j] - pre[i - 1]){
              dp[i][j] = dp[i][k] + dp[k + 1][j] + pre[j] - pre[i - 1];
              s[i][j] = k;
          }
      }
   }
}

int res = 1 << 30;
for(int i = 1; i <= n + 1; i ++ )
    res = min(res, dp[i][i + n - 1]);
cout << res << endl;

记忆化搜索

典型示例 滑雪

const int N = 310;
int n, m, f[N][N], g[N][N];
int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};

int dp(int i, int j){
    int &v = f[i][j];
    if(v != -1) return v;

    v = 1;
    for(int k = 0; k < 4; k ++ ){
        int ni = i + dx[k], nj = j + dy[k];
        if(ni >= 1&& ni <= n&& nj >= 1&& nj <= m && g[ni][nj] < g[i][j])
            v = max(v, dp(ni, nj) + 1);
    }
    return v;
}

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

    memset(f, -1, sizeof(f));
    int ans = 0;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            ans = max(ans, dp(i, j));

    cout << ans;
}

状态压缩DP

//状态压缩DP + 滚动数组优化(Acwing 4406.积木画)

typedef long long ll;
static const int mod = 1e9 + 7;
ll dp[2][3];  //dp[i][j]表示前i列填满并且填了第i+1列j个位置的方案
//第i种状态只与i-1有关,可以用滚动数组优化

int main(){
    int n; cin >> n;
    dp[1][0] = 1, dp[1][1] = 2, dp[1][2] = 1;
    
    for(int i = 2; i <= n; i ++ ){
        dp[i & 1][0] = (dp[i - 1 & 1][0] + dp[i - 1 & 1][2]) % mod;
        dp[i & 1][1] = (dp[i - 1 & 1][0] * 2 + dp[i - 1 & 1][1]) % mod;
        dp[i & 1][2] = (dp[i - 1 & 1][0] + dp[i - 1 & 1][1]) % mod;
    }
    
    cout << dp[n & 1][0] << endl;
}


树形DP

没有上司的舞会 模型

const int N = 6010;
int happy[N], n;
int h[N], e[N], ne[N], idx;
int dp[N][2];   //dp[u][0]表示在以u为根的子树中选择并且不选u,dp[u][1]表示...并且选择u
bool hasfa[N];

void add(int a, int b){
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

void dfs(int u){
    dp[u][1] = happy[u];

    for(int i = h[u]; i != -1; i = ne[i]){
        int j = e[i];
        dfs(j);

        dp[u][0] += max(dp[j][0], dp[j][1]);
        dp[u][1] += dp[j][0];
    }
}

int main(){
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> happy[i];

    memset(h, -1, sizeof(h));
    for(int i = 0; i < n - 1; i ++ ){
        int a, b; cin >> a >> b;
        hasfa[a] = true;
        add(b, a);
    }

    int root = 1;
    while(hasfa[root]) root++;

    dfs(root);

    cout << max(dp[root][0], dp[root][1]);
}

树上最远距离 模型

static const int N = 10010, M = 2 * N;
int h[N], e[M], ne[M], w[M], idx;
int dp[N][2];  //dp[i][0]表示i出发的最长路,dp[i][1]表示i出发的次长路
int n;
int res = -1e9;

void add(int a, int b, int c){
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++ ;
}

void dfs(int u, int fa){
    dp[u][0] = dp[u][1] = 0;
    for(int i = h[u]; i != -1; i = ne[i]){
        int v = e[i], c = w[i];
        if(v == fa) continue;

        dfs(v, u);   //自底向上

        if(dp[v][0] + c > dp[u][0]) dp[u][1] = dp[u][0], dp[u][0] = dp[v][0] + c;
        else if(dp[v][0] + c > dp[u][1]) dp[u][1] = dp[v][0] + c;
    }

    res = max(res, dp[u][0] + dp[u][1]);
}

int main(){
    cin >> n;
    memset(h, -1, sizeof(h));
    for(int i = 1; i <= n - 1; i ++ ){
        int u, v, c; cin >> u >> v >> c;
        add(u, v, c), add(v, u, c);
    }

    dfs(1, -1);

    cout << res;

    return 0;
}

树上最大和模型

static const int N = 1e5 + 10, M = 2 * N;
int h[N], e[M], ne[M], w[N], idx;
int n;
typedef long long ll;
ll dp[N];

void add(int a, int b){
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void dfs(int u, int fa){
    dp[u] = (ll)w[u];
    for(int i = h[u]; i != -1; i = ne[i]){
        int v = e[i];
        if(v == fa) continue;
        dfs(v, u);   //自底向上
        dp[u] += max(0LL, dp[v]);
    }
}

int main(){
    memset(h, -1, sizeof(h));
    cin >> n;
    for(int i = 1; i <= n; i ++ ) cin >> w[i];
    for(int i = 1; i <= n - 1; i ++ ){
        int u, v; cin >> u >> v;
        add(u, v), add(v, u);
    }

    dfs(1, -1);

    ll res = -1e12;
    for(int i = 1; i <= n; i ++ ) res = max(res, dp[i]);

    cout << res;

    return 0;
}
posted @ 2023-04-14 13:45  MarisaMagic  阅读(30)  评论(0编辑  收藏  举报