栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

[Acwing] 算法汇总五 动态规划 一

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

[Acwing] 算法汇总五 动态规划 一

目录
      • 背包问题
        • 01背包问题
        • 完全背包
        • 多重背包问题 I
        • 多重背包问题 II
        • 分组背包
      • 线性DP
        • 最长上升子序列
        • 最长上升子序列 II
        • 最长公共子序列
        • 最短编辑距离
        • 编辑距离
      • 区间DP
        • 石子合并

背包问题 01背包问题

状态表示 :
f [ i ] [ j ] f[i][j] f[i][j] 前 i i i个物品中,体积为 j j j的最大价值

状态转移 :
f [ i ] [ j ] = f [ i − 1 ] [ j ] ( j − v < 0 ) f[i][j] =f[i-1][j](j-v<0) f[i][j]=f[i−1][j](j−v<0)
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − v ] + w [ i ] ) f[i][j] =max(f[i-1][j],f[i-1][j-v]+w[i]) f[i][j]=max(f[i−1][j],f[i−1][j−v]+w[i])

因为我们每次用到的都是第二层的状态,第一层的状态一直没有用到

因此我们可以考虑优化掉第一层

但是因为 f [ i ] [ j ] f[i][j] f[i][j]与 f [ i − 1 ] [ j ] f[i-1][j] f[i−1][j]独立

因此我们需要倒序处理

完全背包

每个物品可以无限用

因为存在无限用的规则,因此有一个暴力写法就是枚举 k ∗ v [ i ] < m k*v[i]

不过可想而知这是 n 3 n^3 n3的

我们由下列等式可知
f [ i , j ] = m a x ( f [ i − 1 , j ] , f [ i − 1 , j − v ] + w , f [ i − 1 , j − 2 ∗ v ] + 2 ∗ w , f [ i − 1 , j − 3 ∗ v ] + 3 ∗ w , . . . . . ) f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w , f[i-1,j-2*v]+2*w , f[i-1,j-3*v]+3*w , .....) f[i,j]=max(f[i−1,j],f[i−1,j−v]+w,f[i−1,j−2∗v]+2∗w,f[i−1,j−3∗v]+3∗w,.....)

f [ i , j − v ] = m a x ( f [ i − 1 , j − v ] , f [ i − 1 , j − 2 ∗ v ] + w , f [ i − 1 , j − 3 ∗ v ] + 2 ∗ w , . . . . . ) f[i , j-v]= max( f[i-1,j-v] , f[i-1,j-2*v] + w , f[i-1,j-3*v]+2*w , .....) f[i,j−v]=max(f[i−1,j−v],f[i−1,j−2∗v]+w,f[i−1,j−3∗v]+2∗w,.....)
得出
f [ i ] [ j ] = m a x ( f [ i , j − v ] + w , f [ i − 1 ] [ j ] ) f[i][j]=max(f[i,j-v]+w , f[i-1][j]) f[i][j]=max(f[i,j−v]+w,f[i−1][j])

第三层循环没必要,则转移过程变为
f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i ] [ j − v ] + w [ i ] ) f[i][j]=max(f[i][j],f[i][j-v]+w[i]) f[i][j]=max(f[i][j],f[i][j−v]+w[i])

因为这里只用到了第 i i i层,因此我们可以反着和 01 01 01背包问题优化

我们只需要正向枚举进行转移即可

 for(int i = 1 ; i<=n ;i++)
    for(int j = v[i] ; j<=m ;j++)//注意了,这里的j是从小到大枚举,和01背包不一样
    {
            f[j] = max(f[j],f[j-v[i]]+w[i]);
    }
多重背包问题 I

将多重背包的各个价值,看作多个价值,然后跑一遍 01 01 01背包 即可

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=m;j>=0;j--)
        {
            for(int k=1;k<=s[i]&&k*v[i]<=j;k++)
                f[j] = max(f[j],f[j-k*v[i]]+k*w[i]);
        }
    }
    cout< 
多重背包问题 II 

将多重背包进行二进制拆分

void solve()
{
    cin>>n>>m;
    for(int i = 1;i<=n;i++)
    {
        int v,w,s;  cin>>v>>w>>s;

            for(int k = 1;k<=s;k*=2)//二进制拆分
            {
                for(int j = m;j>= k*v;j--)
                f[j] = max(f[j],f[j-k*v]+k*w);
                s-=k;
            }

            if(s)
            {
                for(int j = m ;j>=s*v;j--)
                {
                    f[j] = max(f[j],f[j-s*v]+s*w);
                }
            }
    }
    cout< 
分组背包 

有多个组,每组有多个物品,每组只能选一个

因此属于是 01 01 01背包的嵌套

状态表示 :
f [ i ] [ j ] f[i][j] f[i][j] 从前 i i i组物品选,体积小于等于 j j j的最大值
状态计算 :
f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j] =f[i-1][j] f[i][j]=f[i−1][j]
f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j − v [ i ] [ k ] ] + w [ i ] [ k ] ] ) f[i][j]=max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]]) f[i][j]=max(f[i][j],f[i−1][j−v[i][k]]+w[i][k]])

因为只用到了第 i − 1 i-1 i−1层,所以考虑使用 01 01 01背包倒序优化

#include
using namespace std;

const int N=110;
int f[N];
int v[N][N],w[N][N],s[N];
int n,m,k;

int main(){
    cin>>n>>m;
    for(int i=0;i
        cin>>s[i];
        for(int j=0;j
            cin>>v[i][j]>>w[i][j];
        }
    }

    for(int i=0;i
        for(int j=m;j>=0;j--){
            for(int k=0;k    //for(int k=s[i];k>=1;k--)也可以
                if(j>=v[i][k])     f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);  
            }
        }
    }
    cout< 
线性DP 

从上和从下进行转移

但是需要处理边界,对于外围边界都是 − I N F -INF −INF

const int N = 500+10,INF = 0x3f3f3f3f;
const double eps = 1e-5;


struct node{
    int to,val;
};

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

void solve(){
	memset(f,-0x3f,sizeof f);
	int n;cin>>n;
	
	for(int i=1;i<=n;i++){
		for(int j=1;j<=i;j++){
			cin>>a[i][j];
			f[i][j] = a[i][j];
		}
	}
	
	for(int i=2;i<=n;i++){
		for(int j=1;j<=i;j++){
			f[i][j] += max(f[i-1][j],f[i-1][j-1]);
		}
	}
	
	int ans = -INF;
	for(int i=1;i<=n;i++)
	ans = max(f[n][i],ans);
	
	cout< 
最长上升子序列 

状态表示 :
f [ N ] f[N] f[N]以当前 i i i结尾的最大上升子序列长度

状态计算 :
我们 N 2 N^2 N2的枚举 a [ j ] < a [ i ] a[j]a[j] f [ i ] = m a x ( f [ i ] , f [ j ] + 1 ) f[i]=max(f[i],f[j]+1) f[i]=max(f[i],f[j]+1)

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

void solve(){
	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 ans = -INF;
	
	for(int i=1;i<=n;i++) ans = max(ans,f[i]);
	cout< 
最长上升子序列 II 

模板
或者二分的寻找最大的小于 a [ i ] a[i] a[i]的位置进行转移

int main(void) {
    int n; cin >> n;
    vectorarr(n);
    for (int i = 0; i < n; ++i)cin >> arr[i];

    vectorstk;//模拟堆栈
    stk.push_back(arr[0]);

    for (int i = 1; i < n; ++i) {
        if (arr[i] > stk.back())//如果该元素大于栈顶元素,将该元素入栈
            stk.push_back(arr[i]);
        else//替换掉第一个大于或者等于这个数字的那个数
            *lower_bound(stk.begin(), stk.end(), arr[i]) = arr[i];
    }
    cout << stk.size() << endl;
    return 0;
}
最长公共子序列

状态表示 :
f [ i ] [ j ] f[i][j] f[i][j] 表示 a a a中的前 i i i个字母和 b b b中的前 j j j个字母的最长公共子序列长度

状态计算 :
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j − 1 ] , f [ i − 1 ] [ j − 1 ] , f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − 1 ] + 1 ) f[i][j]=max(f[i-1][j-1],f[i-1][j-1],f[i-1][j],f[i-1][j-1]+1) f[i][j]=max(f[i−1][j−1],f[i−1][j−1],f[i−1][j],f[i−1][j−1]+1)

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< 
最短编辑距离 

询问字符串 A A A经过最少次操作之后变为 B B B

操作定义如下 :

  1. 删除-将字符串 A A A中的某个字符删除
  2. 插入-在字符 A A A的某个位置插入某个字符
  3. 替换-将字符串 A A A中的某个字符替换为另一个字符

状态表示 :
f [ i ] [ j ] f[i][j] f[i][j]表示 a a a中的前 i i i个变为 b b b中的前 j j j个字母的操作次数

状态计算 :
f [ i ] [ j ] = f [ i ] [ j − 1 ] + 1 f[i][j] =f[i][j-1]+1 f[i][j]=f[i][j−1]+1添加一个字母
f [ i ] [ j ] = f [ i − 1 ] [ j ] + 1 f[i][j]=f[i-1][j]+1 f[i][j]=f[i−1][j]+1删除一个字母
f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] + 1 f[i][j]=f[i-1][j-1]+1 f[i][j]=f[i−1][j−1]+1替换该字母,因为我们替换的是最后面的那个字母,所有应该从 i − 1 , j − 1 i-1,j-1 i−1,j−1转移过来
f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] f[i][j]=f[i-1][j-1] f[i][j]=f[i−1][j−1]什么也不做

当然显然的,对于初始化所有的 i i i
f [ 0 ] [ i ] = i f[0][i]=i f[0][i]=i同时 f [ i ] [ 0 ] = i f[i][0]=i f[i][0]=i

  scanf("%d%s", &n, a + 1);
    scanf("%d%s", &m, b + 1);

    for (int i = 0; i <= m; i ++ ) f[0][i] = i;
    for (int i = 0; i <= n; i ++ ) f[i][0] = i;

    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
        {
            f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1);
            if (a[i] == b[j]) f[i][j] = min(f[i][j], f[i - 1][j - 1]);
            else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
        }

    printf("%dn", f[n][m]);
    }
编辑距离

显然对于每个字符串之间的转换最优的选择都是选择最短编辑距离,同时又因为时间复杂度支持,因此我们可用完全套用上面的模板

#include 
using namespace std;

const int N = 1010;

int n, m;
int f[N][N];
char s[N][N],str[N];


int get_minn(char a[],char b[])
{
    int len_a = strlen(a+1);
    int len_b = strlen(b+1);
    for (int i = 0; i <= len_b; i ++ ) f[0][i] = i;
    for (int i = 0; i <= len_a; i ++ ) f[i][0] = i;

    for (int i = 1; i <= len_a; i ++ )
        for (int j = 1; j <=len_b; j ++ )
        {
            f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1);
            if (a[i] == b[j]) f[i][j] = min(f[i][j], f[i - 1][j - 1]);
            else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
        }

    return f[len_a][len_b];
}
void solve()
{
    int n,m;
    cin>>n>>m;

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

    while(m -- )
    {
        int ans = 0 ;
        int op = 0 ;
        cin>>str+1>>op;

        for(int i = 1 ;i<=n;i++)
        {
            if(get_minn(s[i],str)<=op)
                ++ans;
        }

        cout<
    ios::sync_with_stdio(false);
    solve();
    return 0;
}
区间DP 石子合并

给定 N N N堆石子,每次合并相邻的两堆,对于每次合并都会消耗 a [ i ] + a [ j ] a[i]+a[j] a[i]+a[j],询问最小的代价

关键点 :
最后一次合并一定是左边连续的一部分和右边连续的一部分进行合并

状态表示 :
f [ i ] [ j ] f[i][j] f[i][j]表示 i i i到 j j j这一段石子合并成一堆的最小花费

状态计算 :
f [ i ] [ j ] = m i n ( f [ i ] [ j ] , f [ i ] [ k ] + f [ k + 1 ] [ j ] + s [ j ] − s [ i − 1 ] ) f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]) f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]−s[i−1])

显然初始化 f [ i ] [ i ] = 0 f[i][i]=0 f[i][i]=0

当然对于处理 区间dp方法也很特殊

枚举长度+左端点,然后枚举切分

    int n;
    cin >> n;

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

    memset(f, 0x3f, sizeof f);
    // 区间 DP 枚举套路:长度+左端点 
    for (int len = 1; len <= n; len ++) { // len表示[i, j]的元素个数
        for (int i = 1; i + len - 1 <= n; i ++) {
            int j = i + len - 1; // 自动得到右端点
            if (len == 1) {
                f[i][j] = 0;  // 边界初始化
                continue;
            }

            for (int k = i; k <= j - 1; k ++) { // 必须满足k + 1 <= j
                f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]);
            }
        }
    }

    cout << f[1][n] << endl;

}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/879295.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号