算法基础课:第五讲——动态规划

常用模型:

背包问题、线性DP、区间DP、状态压缩DP、树型DP、计数类DP、数位统计DP、记忆化搜索(实现方式)

闫氏DP分析法:

集合的角度来思考,一般来说从两个角度来考虑,即状态表示和状态计算。状态表示,思考需要用几维来表示状态,状态计算则考虑如何将状态计算出来。状态表示从两个角度来考虑,一是集合的含义,二是属性:一般取 MAX / MIN / 数量算法基础课:第五讲——动态规划_第1张图片

DP的优化:

对代码或者计算方程做等价变形

背包问题:

01背包:

N件物品、容量为V的背包,每个物品有两个属性vi和wi,每件物品最多用一次,挑一些物品使得总体积<=V,使价值达到最大

题目:AcWing 2. 01背包问题

AcWing 2. 01背包问题

闫氏DP分析法:

算法基础课:第五讲——动态规划_第2张图片

AC代码:

// 朴素版本
#include 
#include 
#include 

using namespace std;

const int N = 1005;

int n, m;
int v[N], w[N], f[N][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 = 1; j <= m; j ++)
        {
            if (v[i] <= j)
                f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
            else
                f[i][j] = f[i - 1][j];
        }
        
    cout << f[n][m] << endl;
    return 0;
}


// 优化版本
#include 
#include 
#include 

using namespace std;

const int N = 1005;

int n, m;
int v[N], w[N], 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 = m; j >= v[i]; j --)
            f[j] = max(f[j], f[j - v[i]] + w[i]);
        
        
    cout << f[m] << endl;
    return 0;
}




完全背包:

每件物品有无限个

题目:AcWing 3. 完全背包问题

AcWing 3. 完全背包问题

闫氏DP分析法:

算法基础课:第五讲——动态规划_第3张图片

AC代码:

// 朴素版本,最坏时间复杂度为O(N³)
#include 
#include 
#include 

using namespace std;

const int N = 1010;

int n, m;
int v[N], w[N], f[N][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 ++)   
            for (int k = 0; k * v[i] <= j; k ++)
            {
                f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
            }
    
    cout << f[n][m] << endl;
    return 0;
}


// 二维优化版本
#include 
#include 
#include 

using namespace std;

const int N = 1010;

int n, m;
int v[N], w[N], f[N][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 
#include 
#include 

using namespace std;

const int N = 1010;

int n, m;
int v[N], w[N], 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 ++)
            f[j] = max(f[j], f[j - v[i]] + w[i]);

    cout << f[m] << endl;
    return 0;
}




多重背包:

每种物品有 si 个

题目:Acwing 4. 多重背包 I

Acwing 4. 多重背包 I
Acwing 5. 多重背包 II

闫氏DP分析法:

算法基础课:第五讲——动态规划_第4张图片

AC代码:

// 朴素做法
#include 
using namespace std;

const int N = 105;

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

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 <= s[i] && k * v[i] <= j; k ++)
                f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + w[i] * k);
    
    
    cout << f[n][m] << endl;
    return 0;
}



// 0 < N < 2000 二进制优化做法
#include 
using namespace std;

const int N = 25000;

int n, m;
int v[N], w[N], s[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 *= 2;
        }
        if (s > 0)  
        {
            cnt ++;
            v[cnt] = s * a;
            w[cnt] = s * b;
    
        }
    }
    
    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;
    return 0;
    
    
    
}




分组背包:

有N组物品,每组物品里有若干个,每一组里至多只能选一个物品(互斥)

题目:Acwing 9. 分组背包问题

Acwing 9. 分组背包问题

闫氏DP分析法:

算法基础课:第五讲——动态规划_第5张图片

AC代码:

#include 
using namespace std;

const int N = 110;

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

int main()
{
    cin >> n >> m;
    
    for (int i = 1; i <= n; i ++)
    {
        cin >> s[i];        // 第i组物品数
        for (int j = 1; j <= s[i]; j ++)    cin >> v[i][j] >> w[i][j];  // 第i组第j件物品的v, w
    }![请添加图片描述](https://img-blog.csdnimg.cn/fadba2356d824a20abbaff7287e12f0d.png)

    
    for (int i = 1; i <= n; i ++)
        for (int j = m; j >= 0; j --)
            for (int k = 0; k <= s[i]; k ++)
                if (v[i][k] <= j)            // 第i组第k件物品容的下
                    f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
    
    cout << f[m] << endl;
    
    return 0;
}

线性DP:

意义:

所有递推顺序有一个模糊的线性顺序

题目: Acwing 898. 数字三角形

Acwing 898. 数字三角形

闫氏DP分析法:

算法基础课:第五讲——动态规划_第6张图片

AC代码:

#include 

using namespace std;

const int N = 550, INF = 1e9;

int n;
int a[N][N];
int f[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 ++)
            f[i][j] = -INF;
            
    f[1][1] = a[1][1];
    
    // 动态规划
    for (int i = 2; i <= n; i ++)
        for (int j = 1; j <= i; j ++)
            f[i][j] = max(f[i - 1][j - 1] + a[i][j], f[i - 1][j] + a[i][j]);          

    
    int res = -INF;
    for (int i = 1; i <= n; i ++)   res = max(res, f[n][i]);
    
    cout << res << endl;

    return 0;
}

题目:Acwing 895. 最长上升子序列

Acwing 895. 最长上升子序列

闫氏DP分析法:

算法基础课:第五讲——动态规划_第7张图片

AC代码:

#include 

using namespace std;

const int N = 1005;

int n;
int a[N], f[N];

int main()
{
    cin >> n;
    
    for (int i = 1; i <= n; i ++)   cin >> a[i];
    
    for (int i = 1; i <= n; i ++)
    {
        f[i] = 1;
        
        for (int j = 1; j <= i; j ++)
            if (a[j] < a[i])
                f[i] = max(f[i], f[j] + 1);
    }
    
    int res = 0;
     
    for (int i = 1; i <= n; i ++)   res = max(res, f[i]);
    
    cout << res << endl;
    
    return 0;
}

题目: AcWing 897. 最长公共子序列

AcWing 897. 最长公共子序列

闫氏DP分析法:

算法基础课:第五讲——动态规划_第8张图片

AC代码:

#include 

using namespace std;

const int N = 1050;
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 ++)
        {
            f[i][j] = max(f[i - 1][j], f[i][j - 1]);
            if (a[i] == b[j])
                f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1);
        }
    
    cout << f[n][m] << endl;
    
    return 0;
}

区间DP:

题目: AcWing 282. 石子合并

AcWing 282. 石子合并

闫氏DP分析法:

算法基础课:第五讲——动态规划_第9张图片

AC代码:

#include 
using namespace std;

const int N = 305;

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 - 1; 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;
    
    return 0;
}

计数类DP:

题目: AcWing 900. 整数划分

AcWing 900. 整数划分

闫氏DP分析法:

算法基础课:第五讲——动态规划_第10张图片

AC代码:

#include 
using namespace std;

const int N = 1050, MOD = 1e9 + 7;

int n;
int f[N];

int main()
{
    cin >> n;
    
    f[0] = 1;
    
    for (int i = 1; i <= n; i ++)
        for (int j = i; j <= n; j ++)
            f[j] = (f[j] + f[j - i]) % MOD;
    
    
    cout << f[n] << endl;
    return 0;
}



#include 
using namespace std;

const int N = 1010, MOD = 1e9 + 7;

int n;
int f[N][N];

int main()
{
    cin >> n;
    f[0][0] = 1;
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= i; j ++)
            f[i][j] =   (f[i - 1][j - 1] + f[i - j][j]) % MOD;
    
    
    int res = 0;
    for (int i = 1; i <= n; i ++)   res = (res + f[n][i]) % MOD;
    
    cout << res << endl;
    
    return 0;
}

数位统计DP:

题目: AcWing 338. 计数问题

AcWing 338. 计数问题

闫氏DP分析法:

算法基础课:第五讲——动态规划_第11张图片

AC代码:

#include 
#include 
#include 

using namespace std;

const int N = 10;

/*

001~abc-1, 999

abc
    1. num[i] < x, 0
    2. num[i] == x, 0~efg
    3. num[i] > x, 0~999

*/

int get(vector<int> num, int l, int r)
{
    int res = 0;
    for (int i = l; i >= r; i -- ) res = res * 10 + num[i];
    return res;
}

int power10(int x)
{
    int res = 1;
    while (x -- ) res *= 10;
    return res;
}

int count(int n, int x)
{
    if (!n) return 0;

    vector<int> num;
    while (n)
    {
        num.push_back(n % 10);
        n /= 10;
    }
    n = num.size();

    int res = 0;
    for (int i = n - 1 - !x; i >= 0; i -- )
    {
        if (i < n - 1)
        {
            res += get(num, n - 1, i + 1) * power10(i);
            if (!x) res -= power10(i);
        }

        if (num[i] == x) res += get(num, i - 1, 0) + 1;
        else if (num[i] > x) res += power10(i);
    }

    return res;
}

int main()
{
    int a, b;
    while (cin >> a >> b , a)
    {
        if (a > b) swap(a, b);

        for (int i = 0; i <= 9; i ++ )
            cout << count(b, i) - count(a - 1, i) << ' ';
        cout << endl;
    }

    return 0;
}

状态压缩DP:

题目: AcWing 91. 最短Hamilton路径

AcWing 91. 最短Hamilton路径

闫氏DP分析法:

算法基础课:第五讲——动态规划_第12张图片

AC代码:

#include 
#include 
#include 
using namespace std;

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

int w[N][N];
int f[M][N];
int 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;    // 表示从 0 到 0 且经过的所有点为{ 0 }的方案
    
    for (int i = 1; i < 1 << n; i ++)   // 二进制表示所有状态,共有2^n 个状态
        for (int j = 0; j < n; j ++)    // 枚举每一个点
            if (i >> j & 1)     // 如果当前方案经过 j 
                for (int k = 0; k < n; k ++)    // 枚举倒数第二个点
                    if (i >> k & 1)     // 若当前方案经过k点
                        f[i][j] = min(f[i][j], f[i - (1 << j)][k] + w[k][j]);       // 更新方案
    
    cout << f[(1 << n) - 1][n - 1] << endl;
    return 0;
}

树形DP:

题目: AcWing 285. 没有上司的舞会

AcWing 285. 没有上司的舞会

闫氏DP分析法:

算法基础课:第五讲——动态规划_第13张图片

AC代码:

#include 
#include 
#include 

using namespace std;

const int N = 6010;
int h[N], e[N], ne[N], idx;
int happy[N];
int f[N][2];
int n;
bool st[N];     // 判断是否为root

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


void dfs(int u)
{
    f[u][1] = happy[u];
    
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];   // j 为 u 的所有子节点
        
        dfs(j);
        
        f[u][0] += max(f[j][0], f[j][1]);
        f[u][1] += f[j][0];
    }
    
}

int main()
{
    cin >> n;   // 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;  // b 是 a 的父节点
        add(b, a);
        st[a] = true;
    }
    
    // 找根节点
    int root = 1;
    while (st[root])    root ++;
    
    
    // 树形DP    
    dfs(root);
    
    // 输出答案
    cout << max(f[root][0], f[root][1]) << endl;
    
    return 0;
}

记忆化搜索:

题目:Acwing 901. 滑雪

Acwing 901. 滑雪

闫氏DP分析法:

算法基础课:第五讲——动态规划_第14张图片

AC代码:

#include 
#include 
#include 

using namespace std;

const int N = 305;

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

int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};  

bool check(int x, int y)
{
    return x >= 1 && x <= n && y >= 1 && y <= m; 
}

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 a = x + dx[i], b = y + dy[i];
        
        if (check(a, b) && w[a][b] < w[x][y])   // 不越界,且下一格子小于当前格子
            v = max(v, dp(a, b) + 1);
    }

    return v;
}


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

你可能感兴趣的:(算法基础课,算法,动态规划,c++)