其他分享
首页 > 其他分享> > 动态规划

动态规划

作者:互联网

动态规划

简介

思想:循环过程中,保证i位置以前都为最优解。

原理:此问题通过局部的最优解,能导致整个问题的全局最优解。(废话)

原则:无后效性,即后面的数确定之后,不会影响(改变)前面的数。

常识
1.动态规划一般用dp[ ]或dp[ ][ ]来表示。
2.


for(int i=0;i<n;i++)
{
	for(int j=0;j<i;j++)//第二个循环小于的值一定是i,后文有解释
	{
		dp[i]=max(dp[i]1.,dp[i-1]+1);//后文解释
	}
}

例子解释:
1.第二个循环小于的值一定是i,因为我们要找的是到第I个时的最优解。(从局部最优解到全局最优解),但这里要注意下除1——i之外也有可能是i——1.
2.max(dp[i]1.,dp[i-1]+1) 这里有两种情况1i的值较大,j的值较小,

注意事项
1.动态规划一般通过一个或两个循环(不固定)进行求解。
2.如果要用两个循环,注意是否会超时,合理选择优化方案。
3.动态规划求解顺序自顶向下,自底向上.

例题分享

最长相同子串,序列。

子串:保证要是连续的。
子序列:不用连续的。
思路:
最长相同子序列:
if 只要发现相同的字符。那么,dp[i][j]这个值就是从dp[i][j]和dp[i-1][j-1]中直接来选取的。
else 而另外一种字符不相同的情况。因为这是二维数组,所以就dp[i][j]有两个上一个分别为dp[i-1][j]和dp[i][j-1],于是就从这两个值中选取最大值。

最长相同子串
与最长相同子序列类似,只不过else(当不满足条件时)不取最大值了。而是要将dp[i][j]变为1

下面贴代码

for(int i=1;i<=n;i++)//n为第一个字符的长度
{
	for(int j=1;j<i;j++)//m为第二个字符的长度
	{
		if(dp[i][j]==dp[i][j-1])
		{
			dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1;
		}else{
			dp[i][j]=max(dp[i-1][j],dp[i][j-1];//这是相同子序列的
			dp[i][j]=1;//这是子串的
		}
	}
}

注意
最长子序列输出的是dp[n][m]
最长子串输出的是数组中的最大的值

动规之背包

1.(普通)01背包

特点: 每个物品只能用一次。

解决: 循环使用逆序–这样问题就可以转换为这个物品放还是不放

划重点: 本题可以使用一维数组。数组的大小为容器的最大容量。这样就可以想成到某容量的时,某物品进来后,减去此物品的容量,加上此物品的价值。看有没有此时该容量的容器。所放置的物品的所有的价值大,取max.

代码如下

int n,m;
cin>>m>>n;
int a[n+1],c[n+1];
for(int i=1;i<=n;i++)
{
    cin>>a[i]>>c[i];
}
int dp[m+1];//这里其实用的是   当i物品进来时,容器内可以存储的最大值
//使局部解构成最优解
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
{
    for(int j=m;j>=a[i];j--)//因为物品只有一个故使用逆序
    {
		if(dp[j-a[i]]+c[i]>dp[j]){//此时的最大容量用通过减去新到物品的容量,加上新的物品的价值与原来不加入此物品的价值相比
    	 dp[j]=dp[j-a[i]]+c[i];
        }
    }
}
cout<<dp[m];

2.完全背包

特点: 每个物品能用无数次。

==解决:==本题因为物品可以使用无数次,所以只用把逆序改为正序就可已了。

这里直接贴代码

int n,m;
cin>>m>>n;
int a[n+1],p[n+1];
for(int i=1;i<=n;i++)
{
    cin>>a[i]>>p[i];
}
 int dp[m+1];
 //10 4
/* 2 1
3 3
4 5
7 9*/
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
{
    for(int j=a[i];j<=m;j++)//完全背包和普通背包唯一的区别在于完全背包要使用顺序,因为物品有无数个所以只需要顺序循环出到此时i的最大值就好    {
        if(dp[j-a[i]]+p[i]>dp[j])//dp[3-2]=1 dp[2]=3 dp[3]=
        {
            dp[j]=dp[j-a[i]]+p[i];
        }
    }
}

3.多重背包

特点: 每个物品能用有限次。

解决: 因为物品只能用有限次数,所以其实可以把本题化为普通背包。这里的转换是要把每一种有多个数量的物品分为多个同种的只有一个的物品
而不能单单将其分为多组普通背包–附图
因为那样的话必须使用三层循环(因为循环到后面,前面的就无法再用了)----时间太长
例如,多重背包中X物品有两个。那么就把它化为两个普通背包中的X物品。
在这里插入图片描述

注意(大体思路):
1.首先确定出物品的总个数—一层循环sum加加
2.两层循环把多重背包转换
3.普通背包

下面贴代码

int n,m;
cin>>n>>m;
int a[n+1][3];
for(int i=1;i<=n;i++)
{
    cin>>a[i][0]>>a[i][1]>>a[i][2];
}
int sum = 0;
for(int i=1;i<=n;i++){
    sum += a[i][2];
}//求出总商品数,这样就可以确定将多重背包转为普通背包后数组的总长度
//预处理 复杂的数据处理简单数据
int w[sum+1],c[sum+1],j1=1;
for(int i=1;i<=n;i++){
    for(int j=1;j<=a[i][2];j++){
        w[j1]=a[i][0];
        c[j1]=a[i][1];
        j1++;
    }
}
int dp[m+1];
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
{
    for(int j=m;j>=a[i];j--)
    {
		if(dp[j-a[i]]+c[i]>dp[j]){
    	 dp[j]=dp[j-a[i]]+c[i];
        }
    }
}

cout<<dp[m];

4.混合背包

这个有两两组合的,也有三个组合的

①普通+完全

特点: 0代表只有1个,1代表它是无穷个

解决: 这个其实只用加一个条件判断,判断是普通背包还是完全背包。

没什么说的,贴代码

int n,m;
cin>>n>>m;
int a[n+1][3];
for(int i=1;i<=n;i++)
{
    cin>>a[i][0]>>a[i][1]>>a[i][2];//分别为重量,价值,数量
}
int dp[m+1];
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
{
    if(!a[i][2])
    {
        for(int j=m;j>=a[i][0];j--)
        {
            if(dp[j-a[i][0]]+a[i][1]>dp[j])
            {
                dp[j]=dp[j-a[i][0]]+a[i][1];
            }
        }
    }else{
        for(int j=a[i][0];j<=m;j++)
        {
            if(dp[j-a[i][0]]+a[i][1]>dp[j])
            {
                dp[j]=dp[j-a[i][0]]+a[i][1];
            }
        }
    }
}
cout<<dp[m];
②多重+完全

不用说,把多重变为普通在按上一个那样

注意: 这个时候要想怎么才能把这两种背包结合到一个数组里,这时数组的大小是多少。

解决:
1.不妨让sum在发现是多重背包的时候。就加上那个物品的个数,如果不是加加就可以了。这就是数组的大小.
2.接着转换的时候。加一个条件判断把多重背包转换为普通背包。再把,完全背包的物品也都放进新的数组

下为代码

int n,m;
cin>>n>>m;
int a[n+1][3];
for(int i=1;i<=n;i++)
{
    cin>>a[i][0]>>a[i][1]>>a[i][2];
}
int sum=0;
for(int i=1;i<=n;i++)
{
    if(a[i][2]!=0)//0是完全背包,非0是多重背包
    {
        sum+=a[i][2];
    }else{
        sum++;
    }
}
int a1[sum+1][3];
int j1=0;
for(int i=1;i<=n;i++)
{
    if(a[i][2]!=0)
    {
        for(int j=0;j<a[i][2];j++)
        {
            a1[j1][0]=a[i][0];
            a1[j1][1]=a[i][1];
            a1[j1][2]=0;
            j1++;
        }
    }else{
        a1[i][0]=a[i][0];
        a1[i][1]=a[i][1];
        a1[i][2]=1;
        j1++;
    }

经典例题

该来的都来了

2019 普及组第三题—纪念品

题目描述
小伟突然获得一种超能力,他知道未来 T 天 N 种纪念品每天的价格。某个纪念品的价格是指购买一个该纪念品所需的金币数量,以及卖出一个该纪念品换回的金币数量。
每天,小伟可以进行以下两种交易无限次:
1.任选一个纪念品,若手上有足够金币,以当日价格购买该纪念品;
2.卖出持有的任意一个纪念品,以当日价格换回金币。
每天卖出纪念品换回的金币可以立即用于购买纪念品,当日购买的纪念品也可以当日卖出换回金币。当然,一直持有纪念品也是可以的。
T 天之后,小伟的超能力消失。因此他一定会在第 TT 天卖出所有纪念品换回金币。
小伟现在有 MM 枚金币,他想要在超能力消失后拥有尽可能多的金币。

题解:
这是动规中一道典型的完全背包问题。

容量就是它今天所拥有的金币数,当然这个容量每天都在变,所以专门定义一个变量ans,卖掉纪念品所得的钱减去买这些纪念品所花的钱

因为此题涉及到天数,所以需要用三层循环。外面是负责天数的循环,里面就是一个纯正的完全背包

贴代码

int t, n, m, ans;
	cin>>t>>n>>m;
	//先输入
	int dp[1000000];
	int price[t+1][n+1]; 
	for (int i = 1; i <= t; ++i) {
	    for (int j = 1; j <= n; ++j) {
	        cin>>price[i][j];
	    }
	}
	//第一天早上手里有m元
	ans = m;
	for (int i = 1; i < t; ++i) {
	    //先把数组赋值为负无穷
	    memset(dp, ~0x3f, sizeof(dp));
	    //什么都不买,今天早上有ans元,明天早上也是ans元
	    dp[ans] = ans;
	    //枚举第j个物品
	    for (int j = 1; j <= n; ++j) {
	        //手里有k元的时候,去推明天早上的钱
	        for (int k = ans; k >= price[i][j]; --k) {//注意1
	            //买一件物品,现金减少,赚一份差价,完全背包倒着循环
	            dp[k - price[i][j]] = max(dp[k - price[i][j]], dp[k] + price[i + 1][j] - price[i][j]);
	        }
	    }
	    //找一下明天早上收益最大
	    int ma = 0;
	    for (int j = 0; j <= ans; ++j) {//注意2
	        ma = max(ma, dp[j]);
	    }
	    //明天早上就有这么多钱了,继续赚钱
	    ans = ma;
	}
	cout << ans << endl;

注意:
1.“- -k"不能是k- =price[i][j],因为当k=price[i][j]时,减后,便为0,会影响最大值。
2.“for循环取最大值”为什么不是最后一个?
因为最后一个值,可能赔钱,可能赚钱。不确定,所以取一个最大的值

动规我们最应该学会什么

如果一个问题让你求解,且此问题有许多方法,途径。
那么动规,动态的寻找不断比较找到一种途径就用min,max比较留下局部最优解。
从而找出最优解(dp数组中最大值或最后一个值)。

动态规划的那个方程是此题的关键。

大神走之前别忘点个赞,加个关注呦!

Albert.Jw 发布了6 篇原创文章 · 获赞 6 · 访问量 2690 私信 关注

标签:背包,int,price,纪念品,物品,动态,规划,dp
来源: https://blog.csdn.net/Albert_Jw/article/details/104019318