小学期算法--DP分析法

一、DP分析法

考虑状态转移的时候

我们要着重考虑”最后一步“,

先考虑到最后一步将进行那些操作,对集合进行划分

然后考虑不进行这最后一步的操作的倒数第二个状态是什么状态

然后考虑进行了这个最后一步的操作之后倒数第二个状态将会对最后一个状态发生怎么样的变化

然后将这个最后一步的操作的贡献和倒数第二个状态相加

然后根据状态表示中的属性对划分出来的集合取Max/Min/数等

二、数字三角形模型

·数字三角形

分析图: 

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 510;

int w[N][N], f[N][N], n;

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= i; j ++)
            cin >> w[i][j];

    memset(f, -0x3f, sizeof f);

    f[0][0] = 0;
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= i; j ++)
            f[i][j] = max(f[i - 1][j - 1] + w[i][j], f[i - 1][j] + w[i][j]);

    int res = 0;
    for(int i = 1; i <= n; i ++)    res = max(res, f[n][i]);
    cout << res << endl;

    return 0;
}

·摘花生

 ·最低通行费

 ·方格取数

 三、背包问题

·01背包问题

分析:

特点:每个物品仅能使用一次
重要变量&公式解释
f[i][j]:表示所有选法集合中,只从前i个物品中选,并且总体积≤j的选法的集合,它的值是这个集合中每一个选法的最大值.
状态转移方程
f[i][j] = max(f[i-1][j], f[i-1][j-v[i]]+w[i])
f[i-1][j]:不选第i个物品的集合中的最大值
f[i-1][j-v[i]]+w[i]:选第i个物品的集合,但是直接求不容易求所在集合的属性,这里迂回打击一下,先将第i个物品的体积减去,求剩下集合中选法的最大值.
问题
集合如何划分

一般原则:不重不漏,不重不一定都要满足(一般求个数时要满足)

如何将现有的集合划分为更小的子集,使得所有子集都可以计算出来.

#include <iostream>

using namespace std;

const int N = 1010;

int n, m;
int v[N], w[N];
int f[N];

int main() {
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> v[i] >> w[i];
    for(int i = 1; i <= n; i++) 
        for(int j = m; j >= v[i]; j--) 
            f[j] = max(f[j], f[j-v[i]]+w[i]);
    cout << f[m] << endl;
 return 0;    
}

·完全背包问题

f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i],f[i-1][j-2v[i]]+2w[i],.....,f[i-1][j-kv[i]]+kw[i])
对照上面的式子,仿写
f[i][j-v[i]]=max(f[i-1][j-v[i]],f[i-1][j-2v[i]]+w[i],......,f[i-1][j-kv[i]]+(k-1)w[i])
通过观察发现max(f[i-1][j-v[i]]+w[i],f[i-1][j-2v[i]]+2w[i],.....,f[i-1][j-kv[i]+kw[i])这一长串
就是f[i][j-v[i]的式子再加上w[i],就是那一长串
所以,f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i])。

上面就是把三维降到二维的原理
而要把二维降到一维,就需要等价变换了。通过对状态转移方程的观察,f[i][j]只需要上一轮f[i-1][j]的值,
和f[i][j-v[i]]的值,而这两个值通过顺序循环都是在f[i][j]计算之前就出现了,而这两个值,
也是通过他们之前的值推导出来的,所以只需知道前面的状态,就可以算出后面的值,不需要其他的没用的值
通过对数组的更新,只保留之前的值,以便于当前值的计算。

#include<iostream>
using namespace std;
const int N = 1010;
int f[N];
int v[N],w[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i = 1 ; i <= n ;i ++)
    {
        cin>>v[i]>>w[i];
    }

    for(int i = 1 ; i<=n ;i++)
    for(int j = v[i] ; j<=m ;j++)
    {
            f[j] = max(f[j],f[j-v[i]]+w[i]);
    }
    cout<<f[m]<<endl;
}

·多重背包问题1

首先思路
现在有a件物品,每件物品都有他自己的价值与体积,因为每件只能放一次,那么咱们就按先后顺序一个一个拿起来判断,看能不能装下,若是能装下,看要不要拿,要不要拿肯定是看此时拿了的话能否构成此时的最优解。要判断是否是该状态下的最优解肯定要判断如果将该物品放入刚好满的话,那放他之前的最优解是什么。转换一下思维就是要求以
背包容积-该物品容积
为容积的背包装之前的物品的最优解【并不包括本物品以及之后的物品】。那么咱们就要通过从1遍历背包的容积来判断最优解,这就是所谓的基于之前的数据。

现在总结下思路
咱们要做这道题就要挨个物品判断要不要拿能不能拿,同时通过遍历背包容积判断每个容积下的最优解,以此来判断该种物品究竟拿了能不能带到此时的最优解。用二维数组dp[i-1][x],就可以完美的表示第i件物品之前容积为x时的最优解(第i件物品和它后面的物品都不可能在这个背包)

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

int a[10005],b[10005],t=0,n,m,dp[10005]={ },w,v,s;
int main()
{
	cin>>n>>m;
	while(n--)
	{
		cin>>v>>w>>s;
		while(s--)
		{
			a[++t]=v;
			b[t]=w;
		}//死拆,把多重背包拆成01背包
	}
	for(int i=1;i<=t;i++)
		for(int j=m;j>=a[i];j--)
			dp[j]=max(dp[j-a[i]]+b[i],dp[j]);//直接套01背包的板子
	cout<<dp[m]<<endl;
	return 0;
}

·多重背包问题2

#include<iostream>
using namespace std;
const int N=11010,M=2010;
int n,m;
int v[N],w[N];
int f[M];
int main()
{
	cin>>n>>m;
	int cnt=1;
	for(int i=1;i<=n;i++)//拆分打包
	{
		int a,b,s;
		cin>>a>>b>>s;
		int k=1;
		while(k<=s)
		{
			v[cnt]=a*k;
			w[cnt]=b*k;
			s-=k;
			k*=2;
			cnt++;
		}
		if(s>0)
		{
			v[cnt]=s*a;
			w[cnt]=s*b;
			cnt++;
		}
	}
	for(int i=1;i<=cnt;i++)
	{
		for(int j=m;j>=v[i];j--)
		{
			f[j]=max(f[j],f[j-v[i]]+w[i]);
		}
	}
	cout<<f[m]<<endl;
	return 0;
}

 ·分组背包问题

 

#include <iostream>
#include <cstring>
#include <algorithm>

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<n;i++){
		cin>>s[i];
		for(int j=0;j<s[i];j++){
			cin>>v[i][j]>>w[i][j];
		}
	}
	
	for(int i=0;i<n;i++){
		for(int j=m;j>=0;j--){
			for(int k=0;k<s[i];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<<f[m]<<endl;
}

·混合背包问题

 

#include <iostream>

using namespace std;

const int N = 1010;

int n, m;
int v[N], w[N], s[N];
int f[N];

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)
    {
        //完全背包
        if (!s[i])
        {
            for (int j = v[i]; j <= m; ++ j)
            {
                f[j] = max(f[j], f[j - v[i]] + w[i]);
            }
        }
        else
        {
            //把多重背包用二进制优化
            //这样就变成做多个01背包了
            if (s[i] == -1) s[i] = 1;
            //二进制优化
            for (int k = 1; k <= s[i]; k *= 2)
            {
                for (int j = m; j >= k * v[i]; -- j)
                {
                    f[j] = max(f[j], f[j - k * v[i]] + k * w[i]);
                }
                s[i] -= k;
            }
            if (s[i])
            {
                for (int j = m; j >= s[i] * v[i]; -- j)
                {
                    f[j] = max(f[j], f[j - s[i] * v[i]] + s[i] * w[i]);
                }
            }
        }
    }

    cout << f[m] << endl;

    return 0;
}

 ·多重背包问题3

 解析参考acwing

AcWing 6. 多重背包问题 III【单调队列优化+图示】 - AcWing

#include <iostream>

using namespace std;

const int N = 1010, M = 20010;

int n, m;
int v[N], w[N], s[N];
int f[2][M];
int q[M];

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 r = 0; r < v[i]; ++ r)
        {
            int hh = 0, tt = -1;
            for (int j = r; j <= m; j += v[i])
            {
                while (hh <= tt && j - q[hh] > s[i] * v[i]) hh ++ ;
                while (hh <= tt && f[(i - 1) & 1][q[tt]] + (j - q[tt]) / v[i] * w[i] <= f[(i - 1) & 1][j]) -- tt;
                q[ ++ tt] = j;
                f[i & 1][j] = f[(i - 1) & 1][q[hh]] + (j - q[hh]) / v[i] * w[i];
            }
        }
    }
    cout << f[n & 1][m] << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值