ACM动态规划模板

动态规划(Dynamic Programming)

      动态规划是一种分阶段求解决策问题的数学思想。一般来说,只要问题可以划分为规模更小的字问题,并且原问题的最优解中包含了子问题的最优解,则可以考虑用动态规划解决

与贪心法的关系:

      1.与贪心法类似,都是将问题实例归纳为更小的、相似的子问题,并通过求解子问题产生一个全局最优解。

      2.贪心法选择当前最优解,而动态规划通过求解局部子问题的最优解来达到全局最优解。

与递归法的关系:

      1.与递归法类似,都是将问题实例归纳为更小的、相似的子问题。

      2.递归法需要对子问题进行重复计算,需要耗费更多的时间与空间,而动态规划对每个子问题只求解一次。对递归法进行优化,可以使用记忆化搜索的方式。它与递归法一样,都是自顶向下的解决问题,动态规划是自底向上的解决问题。

      递归问题——>重叠子问题——>  1.记忆化搜索(自顶向上的解决问题);2.动态规划(自底向上的解决问题)

      关于动态规划更详细的的讲解:https://blog.csdn.net/mmc2015/article/details/73558346

概念

(1)最优子结构      (2)边界      (3)状态转移公式

性质

      最优子结构性质:如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。

      子问题重叠性质:动态规划利用子问题的重叠性质,将需要重复计算的子问题结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。

      无后效性:对于任意阶段的状态,它以前各阶段的状态无法直接影响它未来的决策。换句话说,每个状态都是过去历史的一个完整总结。

基本解题步骤

      (1)找出最优解的性质(最优子结构),并刻划其结构特征。

      (2)递归地定义最优值。

      (3)以自底向上的方式计算出最优值。

      (4)根据计算最优值时得到的信息,写出状态转移方程。

下面是动态规划经典例题

01背包问题

/*--------------------------------------------------
    有 N 件物品和一个容量为 V 的背包。
    放入第 i 件物品耗费的空间是 w i ,得到的价值是 v i 。
    求解将哪些物品装入背包可使价值总和最大。 
    要求恰好装满背包,在初始化时 dp[0] = 0 ,其它dp[1~V]均设为 −∞ 。 
    没有要求必须把背包装满,初始化时应该将 F[0~V] 全部设为 0 。 
    求方案数时,在初始化时F[0] = 1,其他F[1~V]均设为0。
--------------------------------------------------*/

int w[N], v[N], dp[V+1], V;
//w[]物品所占容量; v[]物品的价值; V为背包容量
int zeroOne() {
    memset(dp, 0, sizeof(dp));
    for(int i = 0; i < N; i++) { //第i件物品
        for(int j = V; j >= w[i]; j--) { //填满空间j
            dp[j] = max(dp[j], dp[j-w[i]] + v[i]);
         }
    }
    return dp[V];
}

完全背包

/*--------------------------------------------------
    有 N 种物品和一个容量为 V 的背包,每种物品都有无限件可用。
    放入第 i 种物品的耗费的空间是 w i ,得到的价值是 v i 。
    求解:将哪些物品装入背包,可使这些物品的
    耗费的空间总和不超过背包容量,且价值总和最大。
--------------------------------------------------*/

int w[N], v[N], dp[V+1], V;
int Complete() {
    memset(dp, 0, sizeof(dp));
    for(int i = 0; i < N; i++) {
        for(int j = c[i]; j <= V; j++) {
            dp[j] = max(dp[j], dp[j-w[i]] + v[i]);
        }
    }
    return dp[V];
}

多重背包

/*--------------------------------------------------
    有 N 种物品和一个容量为 V 的背包。
    第 i 种物品最多有 M i 件可用,每件耗费的空间是 w i ,价值是 v i 。
    求解将哪些物品装入背包可使这些物品的
    耗费的空间总和不超过背包容量,且价值总和最大。
    二进制优化做法:
--------------------------------------------------*/

int w[a], v[a], m[a], dp[b], V;
//w[]:物品所占容量; v[]物品的价值; m[]物品的数量; V为背包容量
void ZeroOne(int w, int v) {//01背包
    for(int i = V; i >= w; i--)
        dp[i] = max(dp[i], dp[i-w]+v);
}

void Complete(int w, int v) {//完全背包
    for(int i = w; i <= V; i++)
        dp[i] = max(dp[i], dp[i-w]+v);
}

void Multiple(int w, int v, int m) {//多重背包
    //如果总容量比这个物品的容量要小,那么这个物品可以直接取完,相当于完全背包
    if(V <= m*w) {
        Complete(w, v);
        return;
    } else { //否则就将多重背包转化为01背包
        int k = 1;
        while(k <= m) {
            ZeroOne(k*w, k*v);
            m -= k;
            k <<= 1;
        }
        ZeroOne(m*w, m*v);
    }
}

int main() {
    // for(int i = 0; i <= V; i++) //初始化,不要求恰好装满背包
    //    dp[i] = 0;
    for(int i = 0; i <= V; i++) //初始化:是否恰好装满背包
        dp[i] = -0xffffff0;
    dp[0] = 0;
    for(int i = 0; i < N; i++)
        Multiple(w[i], v[i], m[i]);
    return 0;
}

混合背包

/*--------------------------------------------------
    背包体积为V ,给出N个物品,每个物品占用体积为wi,价值为vi,
    每个物品要么至多取1件,要么至多取mi件(mi > 1) , 
    要么数量无限 , 在所装物品总体积不超过V的前提下
    所装物品的价值的和的最大值是多少?
--------------------------------------------------*/

int V, n,w[a],v[a],m[a], dp[b];

for(int i=1; i<=n; i++) {
    if(m[i] == -1) { //完全背包 -1表示无限取
        for(int j=w[i]; j <= V; j++)
            dp[j] = max(dp[j], dp[j-w[i]] + v[i]);
    } else {         //01与多重背包 
        for(int k=1; k <= m[i]; k++)
            for(int j=V; j >= w[i]; j--)
                dp[j] = max(dp[j], dp[j-w[i]] + v[i]);
    }
}

二维费用背包

int w1[a], w2[a], v[a], dp[b][b], V1, V2;
memset(dp, 0, sizeof(dp));  
int Costknapsack() {
    for(int i = 0; i < N; i++) { //第i个  
        for(int j = w1[i]; j <= V1; j++) { //一维费用 
            for(int k = w2[i]; k <= V2; k++) { //二维费用 
                dp[j][k] = max(dp[j][k],dp[j-w1[i]][k-w2[i]] + v[i]);  
            }  
        }  
    }
    return dp[V1][V2];
}  

分组背包

/*--------------------------------------------------
    有N件物品,告诉你这N件物品的重量以及价值,
    将这些物品划分为K组,每组中的物品互相冲突,最多选一件,
    求解将哪些物品装入背包可使这些物品的
    费用综合不超过背包的容量,且价值总和最大。
--------------------------------------------------*/
int a[B][B];
int Groupingbackpack() {
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            scanf("%d", &a[i][j]);

    for(i = 1; i <= n; i++) //第一重循环:分组数
        for(j = V; j >= 0; j--) //第二重循环:容量体积
            for(k = 0; k <= j; k++) //第三重循环:属于i组的k
                dp[j] = max(dp[j], dp[j-k]+a[i][k]);
    return dp[V];
}

K优解

int kth(int n, int V, int k) {
    for (int i = 1; i <= n; i++) {
        for (int j = V; j >= w[i]; j--) {
            for (int l = 1; l <= k; l++) {
                a[l] = dp[j][l];
                b[l] = dp[j - w[i]][l] + v[i];
            }
            a[k + 1] = -1;
            b[k + 1] = -1;
            int x = 1, y = 1, o = 1;
            while (o != k + 1 and (a[x] != -1 or b[y] != -1)) {
                if (a[x] > b[y]) dp[j][o] = a[x], x++;
                else dp[j][o] = b[y], y++;
                if (dp[j][o] != f[j][o - 1]) o++;
            }
        }
    }
    return dp[V][k];
}

最长公共子序列长度

/*--------------------------------------------------
    给定两个序列,找出在两个序列中同时出现的最长子序列的长度。
    一个子序列是出现在相对顺序的序列,但不一定是连续的。
--------------------------------------------------*/
char s1[MAX_N],s2[MAX_M];
int dp[MAX_N+1][MAX_M+1];

int lcs(char *s1, char *s2) {
    int len1 = strlen(s1);
    int len2 = strlen(s2);
    for(int i = 0; i < len1; i++) {
        for(int j = 0; j < len2; j++) {
            if(s1[i] == s2[j]) {
                dp[i+1][j+1] = dp[i][j] + 1;        
            } else {
                dp[i+1][j+1] = max(dp[i][j+1], dp[i+1][j]);
            }
        }
    }
    return dp[len1][len2];
}

最长递增子序列的长度O(NlogN)

int n;
int a[MAX_N], dp[MAX_N];
void lis() {
    fill(dp, dp+n, INF);
    for(int i=0; i < n; i++) {
        *lower_bound(dp, dp+n, a[i]) = a[i];
    }
    printf("%d\n", lower_bound(dp, dp+n, INF) - dp);
}

最大连续子序列和

int n, a[n];
int MaxSubSequence() {
    int ThisSum = 0,MaxSum = 0;
    for(int i=0; i < n; i++) {
        ThisSum += a[i];

        if(ThisSum > MaxSum)
            MaxSum = ThisSum;
        else if(ThisSum < 0)
            ThisSum = 0; 
    }
    return MaxSum; 
} 

最大连续子矩阵和

/*==================================================*\ 
 | 给你一个N,接下来是N*N的矩阵。数有正有负,求最大的子矩阵和。
\*==================================================*/

const int maxn=100;
int max_sub_matrix(int x[][maxn], int m, int n) {
    // m和n分别代表行和列
    int sum = -1000000;     // 选择一个足够小的数
    int p[maxn];    // 开辟一个用于存放和的一维数组
    int dp[maxn];
    for (int i = 0; i < m; ++i){
        memset(p, 0, sizeof p);
        for (int j = i; j < m; ++j){
            for (int k = 0; k < n; ++k){
                p[k] += x[j][k];   // p[k]为第k列,从第i行到第j行的和
            }
 
            // 以下for循环等价于 sum = max(sum, max_sub_array(p, n))
            dp[0] = p[0];
            for (int k = 1; k < n; ++k){
                dp[k] = max(dp[k - 1] + p[k], p[k]);
                sum = max(sum, dp[k]);
            }
        }
    }
    return sum;
}

最大M个连续子段的和

int dp[1000010];
int maxn[1000010];
int num[1000010];
int main() {
    int M,N;
    while(~scanf("%d%d",&M,&N)) {
        dp[0] = maxn[0] = 0;
        for(int i = 1; i <= N; i++)
        {
            scanf("%d",&num[i]);
            dp[i] = maxn[i] = 0;
        }
        int MAXN;
        for(int i = 1; i <= M; i++)//分为i段
        {
            MAXN = -0xffffff0;
            for(int j = i; j <= N; j++)//第j个数字
            {
                dp[j] = max(dp[j-1]+num[j],maxn[j-1]+num[j]);
                maxn[j-1] = MAXN;
                MAXN = max(MAXN,dp[j]);
            }
        }
        printf("%d\n",MAXN);
    }

    return 0;
}

最大不连续子序列和

/*==================================================*\ 
 | 给你一个矩阵,不能选择每行中相邻的数字,也不能选
 | 当前行的上一行和下一行,问使所选数和最大的值是多少? 
 | 对于每一行,都是求最大不连续子段和。
\*==================================================*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAXN = 200000;
int dpa[MAXN+20],dpb[MAXN+20],row[MAXN+20];
int main()
{
    int M,N,num;
    while(~scanf("%d%d",&M,&N))
    {
        memset(row,0,sizeof(row));
        for(int i = 0; i < M; i++)
        {
            dpa[0] = dpb[0] = 0;
            for(int j = 0; j < N; j++)
            {
                scanf("%d",&num);
                dpa[j+1] = max(dpa[j],dpb[j]);// dp[j+1] 是到j为止,不吃j所能吃到的最大值
                dpb[j+1] = dpa[j] + num;//吃j所能吃到的最大值
            }
            row[i] = max(dpa[N],dpb[N]);
        }
        dpa[0] = dpb[0] = 0;
        for(int i = 0; i < M; i++)
        {
            dpa[i+1] = max(dpa[i],dpb[i]);
            dpb[i+1] = dpa[i] + row[i];
        }
        int ans = max(dpa[M],dpb[M]);
        printf("%d\n",ans);
    }
    return 0;
}

最长回文子序列

/*==================================================*\ 
 | 给一个字符串,找出它的最长的回文子序列LPS的长度。
 | 例如,如果给定的序列是“BBABCBCAB”,则输出应该是7,
 | “BABCBAB”是在它的最长回文子序列。
\*==================================================*/

char s[MAX_N];
int dp[MAX_N][MAX_N]; //dp[i][j]表示s[i~j]最长回文子序列

int LPS(char *s) {
    memset(dp,0,sizeof(dp));
    int len = strlen(s);
    for(int i = len-1; i >= 0; --i) {
        dp[i][i] = 1;
        for(int j = i+1; j < len; ++j) {
            //头尾相同,最长回文子序列为去头尾的部分LPS加上头和尾
            if(s[i] == s[j])    
                dp[i][j] = dp[i+1][j-1] + 2;
            //头尾不同,最长回文子序列是去头部分的LPS和去尾部分LPS较长的
            else   
                dp[i][j] = max(dp[i][j-1],dp[i+1][j]);
        }
    }

    return dp[0][len-1];
}

最长回文子串

/*==================================================*\ 
 | 给一个字符串,找出它的最长的回文子串(连续的串)的长度。
\*==================================================*/
string longestPalindrome(string s) {
    const int n = s.size();
    bool dp[n][n];
    memset(dp, 0, sizeof(dp));

    int maxlen = 1;     //保存最长回文子串长度
    int start = 0;      //保存最长回文子串起点
    for(int i = 0; i < n; ++i) {
        for(int j = 0; j <= i; ++j) {
            if(i - j < 2) {
                dp[j][i] = (s[i] == s[j]);
            } else {
                dp[j][i] = (s[i] == s[j] && dp[j + 1][i - 1]);
            }
            if(dp[j][i] && maxlen < i - j + 1) {
                maxlen = i - j + 1;
                start = j;
            }
        }
    }
    return s.substr(start, maxlen);
}

切割钢条

/*==================================================*\ 
 | 给定一段长度为n英寸的钢条和一个价格表Pi,求切割方案
 | 使得销售收益Rn最大。
\*==================================================*/

//自底向上法  
int p[110],r[110]; // r[n]来保存子问题  

int BOTTOM_UP_CUT_ROD(int n) {  
    r[0] = 0; //长度为0的钢条没有收益  
    // 对i=1,2,3,…,n按升序求解每个规模为i的子问题。 
    for(int i = 1; i <= n; i++) {  
        int q = -INF;  
        for(int j = 1; j <= i; j++) {  
            // 直接访问数组r[i-j]来获得规模为j-i的子问题的解 
            q = max(q, p[j]+r[i-j]); 
        }  
        r[i] = q;  
    }  
    return r[n];  
}

状压dp

/*==================================================*\ 
 | 问题引入:在 n*n(n≤20)的方格棋盘上放置 n 个棋子
 | 要求每行每列只放一个,求使它们不能互相攻击的方案总数
\*==================================================*/

int dp[1<<21];
int main() {
    int n, temp;
    scanf("%d", &n);
    dp[0] = 1;//边界条件

    for(int s = 1; s <= (1<<n)-1; s++) {
        dp[s] = 0;
        for(int i = 1; i <= n; i++)
            if(s & (1<<(i-1))) {         //排除掉第i行所有不能放置的位置之后的可放位置
                temp = s ^ (1<<(i-1));   //可以得到s状态的状态
                dp[s] += dp[temp];         //s状态下的方案数
            }
    }
    printf("%d", dp[(1<<n)-1]);
    return 0;
}

区间dp

/*==================================================*\ 
 | 区间dp就是在区间上进行动态规划,求解一段区间上的最优解。
 | 主要是通过合并小区间的 最优解进而得出整个大区间上最优解的dp算法。
\*==================================================*/

for(int len = 1;len<=n; len++) {     //枚举长度
    for(int j = 1;j+len<=n+1; j++) { //枚举起点,ends<=n
        int ends = j+len - 1;
        for(int i = j;i<ends; i++) { //枚举分割点,更新小区间最优解
            dp[j][ends] = min(dp[j][ends], dp[j][i]+dp[i+1][ends]+something);
        }
    }
}

集合DP

/*==================================================*\ 
 | 对于空间里有n个点,给定n个点间的距离,将n个点两两配成n/2对,
 | 使得所有点对距离之和最小(n<=20)
\*==================================================*/
for(int S=0;S<(1<<n);S++) {
    dp[S] = 0x3f3f3f3f;
    for(int i=0; i < n; i++) {
        if((1<<i) & S) break;
        for(int j=0; j<n; j++) if((1<<j) & S) {
            dp[S] = min(dp[S], dp[S^(1<<i)^(1<<j)]+dist[i][j]);
        }
    }
}

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

代码多源于网络,更多模板

 

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值