CSP-J/S 动态规划模版讲解

                               CSP-J/S 动态规划模版讲解

                                        https://algo.codefather.cn/https://algo.codefather.cn/

part 1:动态规划起源与理解

起源

在一次信奥赛中一位选手使用一种新算法秒掉了其他选手,该算法时间复杂度比那些O(N2)快多了,定名为动态规划

理解

实际上就是做出一个表格,每一个代表一个状态,所以做这个最需要的就是状态转移方程
其实动态规划(Dynamic Programming)是一种通过将复杂问题分解为更简单的子问题来解决问题的方法。它适用于有重叠子问题和最优子结构性质的问题。

part 2:为什么讲动态规划而不是其他算法

请看2019~2023年CSP第一轮考试知识点

年份阅读程序1阅读程序2阅读程序3完善程序1完善程序2
2019枚举递推分治递归计数排序
2020枚举枚举质因数分解约瑟夫环二分
2021递归动规二分质因数分解贪心
2022位运算动规二分递推广搜
2023枚举字符串质因数分解二分动规

可以看到,在2019~2023年间二分考了4次,枚举考了3次,而动规也考了3次。
但是为什么不讲二分或枚举呢?
很简单,因为枚举在编程题不经常考,
而二分只是一种思想,并没有固定代码模版 (换句话说就是不好讲)
可以看到,动规在一轮考的次数不算少,所以二轮占的比重一定很大(基本3,4题里面肯定有一道)。再看看历年真题,大部分题目不想超时就得用特殊的方法,动规就是其中的一种

part 3:代码模版

第一类:背包问题

听到他一定很耳熟,大部分背包问题都是用动规求解
代码都很相似,不要搞错了

1.1 01背包

就是这种东西要么不装要么装

二维状态转移方程:
for(int i=1;i<=n;i++){
    for(int j=1;j<=V;j++){
        f[i][j]=f[i-1][j];
        if(v[i]<=j){
            f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
        }
    }
}
cout<<f[n][V];
一维状态转移方程:
for(int i=1;i<=n;i++){
    for(int j=V;j>=v[i];j--){
        f[j]=max(f[j],f[j-v[i]]+w[i]);
    }
}
cout<<f[V];
1.2 完全背包
二维状态转移方程:
for(int i=1;i<=n;i++){
    for(int j=1;j<=V;j++){
        f[i][j]=f[i-1][j];
        if(j>=v[i]){
            f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
        }
    }
}
cout<<f[n][V];
一维状态转移方程:
for(int i=1;i<=n;i++){
    for(int j=v[i];j<=V;j++){
        f[j]=max(f[j],f[j-v[i]]+w[i]);
    }
}
cout<<f[V];
1.3 多重背包
二维状态转移方程:
for(int i=1;i<=n;i++){
    for(int j=V;j>=v[i];j--){
        for(int k=0;k<=s[i]&&k*v[i]<=j;i++)
            f[j]=max(f[j-k*v[i]]+k*w[i]);
    }
}

上述代码时间复杂度约为:
O(n⋅m⋅s),很容易超时

多重背包二进制优化:转为二进制,用01背包求解
for(int i=1;i<=n;i++){
    int vi,wi,si,t=1;
    cin>>vi>>wi>>si;
    while(si>=t){
        v[++cnt]=vi*t;
        w[cnt]=wi*t;
        si-=t;
        t*=2;
    }
    if(s>0){
        v[++cnt]=vi*si;
        w[cnt]=wi*si;
    }
}
for(int i=1;i<=cnt;i++){
    for(int j=V;j>=v[i];j--)
        f[j]=max(f[j],f[j-v[i]]+w[i]);
}
cout<<f[V];

这样就可以减少一层循环,比原来的快约s倍

1.4 分组背包
for(int i=1;i<=n;i++){
    for(int j=V;j>=1;j--){
        for(int k=1;k<=s[i];k++){
            if(j>=v[i][k])
            	f[j]=max(f[j],f[j-v[i][k]]+v[i][k]);
        }
    }
}
cout<<f[V];

第二类:线性DP

2.1:数字三角形
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]);
    }
}
2.2:最长上升子序列LIS
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 ans=0;
for(int i=0;i<=n;i++)
    ans=max(ans,f[i]);
cout<<ans;

第三类:区间DP

3.1:石子合并

描述

在一个操场上一排地摆放着N堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。
设计一个程序,计算出将N堆石子合并成一堆的最小得分。

输入描述

第一行为一个正整数N (2≤N≤100);
以下N行,每行一个正整数,小于10000,分别表示第i堆石子的个数(1≤i≤N)。

输出描述

为一个正整数,即最小得分。

用例输入 1
7
13
7
8
16
21
4
18
用例输出 1
239
来源

动态规划 区间动归

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;
        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];

THE END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值