首页 > 代码库 > 读书笔记 - 其他经典动态规划问题

读书笔记 - 其他经典动态规划问题

当然,还有LIS, 不过之前总结过了,这次就不贴了

/**
给定两个字符串s1s2s3...sn和t1t2t3...tm
求这两个字符串最长的公共子序列的长度

dp[i][j]表示s序列考虑si,t序列考虑tj时的最长公共子序列

状态转移方程:
s[i] == t[j] : dp[i][j] = dp[i-1][j-1] + 1
s[i] != t[j] : dp[i][j] = max(dp[i-1][j], dp[i][j-1])
*/

#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
const int MAXN = 1000 + 5;

char s[MAXN], t[MAXN];
int dp[MAXN][MAXN];

int solve()
{
    scanf("%s%s", s + 1, t + 1);
    int n = strlen(s + 1);
    int m = strlen(t + 1);
    memset(dp, 0, sizeof(dp));
    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][j-1], dp[i-1][j]);
        }
    }
    printf("%d\n", dp[n][m]);
    return dp[n][m];
}

int main()
{
    solve();
    return 0;
}

 

/**
有n种不同大小的数字,每种mi个,判断是否可以从这些数字中选出若干个
让他们的和恰好为K

问题规模:
 1 <= n <= 100
 1 <= ai,mi <= 1e5
 1 <= K <= 1e5

 思路一:
 dp[i][j] 表示前i种物品挑选若干个是否可以组成和是j
 状态转移方程: dp[i][j] = dp[i][j] | dp[i-1][j - k*ai] ( j >= k*ai, 0 <= k <= mi)
 复杂度O(K*sum(mi)) 还是比较高的

 思路二:
 dp[i][j] 表示前i中物品加和得到j的情况下,mi个ai最多能剩多少个,如果不能得到j,值为-1
 状态转移方程:

 dp[i][j] = mi, if  dp[i-1][j] >= 0
 dp[i][j] = -1, if  j < ai || dp[i][j - ai] <= 0
 dp[i][j] = dp[i][j - ai] + 1, else ...

 复杂度是O(nK)
*/
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
const int MAXK = 1e5 + 5;
const int N = 100 + 5;
int n, K;
int m[N], a[N];
int dp1[N][MAXK];
int dp2[MAXK];

void solve1()
{
    memset(dp1, 0, sizeof(dp1));
    dp1[0][0] = 1;
    for(int i = 1; i <= n; i++)
    {
        for(int j = 0; j <= K; j++)
        {
            for(int k = 0; k <= m[i]; k++)
            {
                if(j - k * a[i] >= 0)
                    dp1[i][j] |= dp1[i-1][j - k * a[i]];
            }
        }
    }
    if(dp1[n][K]) printf("Yes\n");
    else printf("No\n");
}

void solve2()
{
    memset(dp2, -1, sizeof(dp2));
    dp2[0] = 0;
    for(int i = 1; i <= n; i++)
    {
        for(int j = 0; j <= K; j++)
        {
            if(dp2[j] >= 0)
                dp2[j] = m[i];
            else if(j < a[i] || dp2[j - a[i]] <= 0)
                dp2[j] = -1;
            else
                dp2[j] = dp2[j - a[i]] + 1;
        }
    }
    if(dp2[K] >= 0) printf("Yes\n");
    else printf("No\n");
}


int main()
{
    scanf("%d%d", &n, &K);
    for(int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    for(int j = 1; j <= n; j++)
        scanf("%d", &m[j]);
    solve1();
    //solve2();
    return 0;
}

 

/**
有n个无区别的物品,将它们划分成不超过m组,求划分方法的个数,对M取模
问题规模:
1 <= m <= n <= 1000
2 <= M <= 10000

DP方法:
dp[i][j] 表示j的i划分.
j的i划分,将j划分成A1,A2,...,Ai,那么在每一个A中取出一个,就变成了
j-i的i划分。
如果Ak == 0,那么就成了j的i-1划分了
所以有递推式
dp[i][j] = dp[i][j-i] + dp[i-1][j];
*/
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
const int N = 1000 + 5;
int n, m;
int dp[N][N];

void solve()
{
    dp[0][0] = 1;
    for(int i = 1; i <= m; i++)
    {
        for(int j = 1; j <= n; j++)
        {
            if(j - i >= 0)
                dp[i][j] = dp[i-1][j];
            else
                dp[i][j] = (dp[i-1][j] + dp[i][j-i]) % M;
        }
    }
    printf("%d\n", dp[m][n]);
}

int main()
{
    scanf("%d%d", &n, &m);
    solve();
    return 0;
}

 

/**
多重集组合数问题
有n种物品,第i种物品有ai个。不同种类的物品可以相互区分,但是同种类的物品是无法区分的
从这些物品中取出m个的话,有多少种取法,方法数模M
问题规模:
1 <= n <= 1000
1 <= m <= 1000
1 <= ai <= 1000
2 <= M <= 10000

//本题的方法学到了一种化简的思路
动态规划法:
dp[i][j] 表示的是考虑第i种物品,取出了j个的取法数
得到状态转移方程:
dp[i][j] = sum(dp[i-1][j-k]) ,0 <= k <= ai 且 j >= k
当然,这样的转移方程很容易得到,求解也很容易,但是需要三重循环。
时间复杂度是O(sum(ai)*m),这题应该计算量大概是1e9的数量级,有可能会卡过,但是很玄
这个时候需要来化简这个转移方程
sum(dp[i-1][j-k]) , 0 <= k <= min(j, ai)
sum(dp[i-1][j-k]) = sum(dp[i-1][j-1 - k]) + dp[i-1][j] - dp[i-1][j-1 - ai], 0 <= k <= min(j-1, ai)
这样就有 dp[i][j] = dp[i][j-1] + dp[i-1][j] - dp[i-1][j-1 - ai]
复杂度就变成O(nm),完全可以应付
*/
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
const int INF = 0x3f3f3f3f;
typedef long long LL;

const int N = 1000 + 5;
const int M = 1000 + 5;

int n, m;
int a[N];
int dp[N][MAXM];
int M;

int read()
{
    scanf("%d%d%d", &n, &m, &M);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
    }
}

void solve()
{
    for(int i = 0; i <= n; i++)
        dp[i][0] = 1;
    for(int i = 1; i <= n; i++)
    {
        for(int j = 0; j <= m; j++)
        {
            if(j - 1 -a[i] >= 0)
                dp[i][j] = (dp[i][j-1] + dp[i-1][j] - dp[i-1][j - 1 - a[i]] + M) % M;
            else
                dp[i][j] = (dp[i][j-1] + dp[i-1][j]) % M;
        }
    }
    printf("%d\n", dp[n][m]);
}

int main()
{
    solve();
    return 0;
}

读书笔记 - 其他经典动态规划问题