动态规划二

复健\(Day4\)

动态规划(二)背包\(DP\)

\(1.01\)背包

每个物品只有一件

#include<iostream>
#include<cstdio>
using namespace std;

const int N=1010;

int dp[N],v[N],w[N];

int main()
{
    int n,V;
    cin>>n>>V;
    for(int i=1;i<=n;i++)
    {
        cin>>v[i]>>w[i];
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=V;j>=v[i];j--) dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
    }
    printf("%d\n",dp[V]);
    return 0;
}

\(2.\)完全背包

每个物品有无穷件

#include<iostream>
#include<cstdio>
#define maxn 1010
using namespace std;

int f[maxn],n,V;
int v[maxn],w[maxn];

int main()
{
    cin>>n>>V;
    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<=V;j++) f[j]=max(f[j],f[j-v[i]]+w[i]);
    }
    printf("%d\n",f[V]);
    return 0;
}

\(3.\)多重背包

每个物品有有限件

二进制优化代码,时间复杂度为\(O(V\sum logs[i])\)(朴素算法是\(O(V\sum s[i])\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 2010
using namespace std;

int dp[maxn],v[maxn],w[maxn],s[maxn],nv[maxn],nw[maxn],cnt;

int main()
{
    int n,V;
    cin>>n>>V;
    for(int i=1;i<=n;i++)
    {
        cin>>v[i]>>w[i]>>s[i];
        int k;
        for(k=1;s[i]-(1<<k)+1>0;k++)
        {
            nv[++cnt]=(1<<(k-1))*v[i];
            nw[cnt]=(1<<(k-1))*w[i];
        }
        k--;
        nv[++cnt]=(s[i]-(1<<k)+1)*v[i];
        nw[cnt]=(s[i]-(1<<k)+1)*w[i];
    }
    for(int i=1;i<=cnt;i++)
    {
        for(int j=V;j>=nv[i];j--) dp[j]=max(dp[j],dp[j-nv[i]]+nw[i]);
    }
    printf("%d\n",dp[V]);
    return 0;
}

单调队列优化代码,时间复杂度为\(O(nV)\)

\(f\)数组是按类更新的,就将其按照体积\(v[i]\)的余数拆分为\(v\)个类

同时,\(f[j]\)是由前面不超过数量\(s[j]\)的同类只递推得到,故我们还可以通过单调队列来维护窗口最大值

因为我们这种更新时是需要顺序更新的,二我们普通的算法中,\(f\)时逆序更新的,那么我们可以备份一个\(g\)数组,用来更新即可

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 20010
using namespace std;

int f[maxn],g[maxn],q[maxn];
int n,V;
int v,w,s;

int main()
{
	cin>>n>>V;
	for(int i=1;i<=n;i++)
	{
		cin>>v>>w>>s;
		memcpy(g,f,sizeof(f));
		for(int j=0;j<v;j++)//j是余数
		{
			int h=1,t=0;
			for(int k=j;k<=V;k+=v)//对每个类使用单调队列
			{
				if(h<=t&&q[h]<k-s*v) h++;//q[h]不在窗口[k-s*v,k-v]内,出队
				if(h<=t) f[k]=max(g[k],g[q[h]]+(k-q[h])/v*w);
				while(h<=t&&g[k]>=g[q[t]]+(k-q[t])/v*w) t--;
				q[++t]=k;
			}
		}
	}
	printf("%d\n",f[V]);
	return 0;
}

\(4.\)混合背包

#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 10010
using namespace std;

int f[maxn],nv[maxn],nw[maxn],ns[maxn];
int cnt;
int n,V;

int main()
{
	cin>>n>>V;
	int v,w,s;
	for(int i=1;i<=n;i++)
	{
		cin>>v>>w>>s;
		if(s==-1)
		{
			nv[++cnt]=v;
			nw[cnt]=w;
			ns[cnt]=-1;
		}
		else if(s==0)
		{
			nv[++cnt]=v;
			nw[cnt]=w;
			ns[cnt]=0;
		}
		else
		{
			int k;
			for(k=1;s-(1<<k)+1>0;k++)
			{
				nv[++cnt]=(1<<(k-1))*v;
				nw[cnt]=(1<<(k-1))*w;
				ns[cnt]=-1;
			}
			k--;
			nv[++cnt]=(s-(1<<k)+1)*v;
			nw[cnt]=(s-(1<<k)+1)*w;
			ns[cnt]=-1;
		}
	}
	for(int i=1;i<=cnt;i++)
	{
		if(ns[i]==-1)
		{
			for(int j=V;j>=nv[i];j--) f[j]=max(f[j],f[j-nv[i]]+nw[i]);
		}
		else
		{
			for(int j=nv[i];j<=V;j++) f[j]=max(f[j],f[j-nv[i]]+nw[i]);
		}
	}
	printf("%d\n",f[V]);
	return 0;
}

但是这里要特别注意一个点,就是我们数组的大小要开大一点,因为我们对多重背包采用了二进制优化,会使数量变多

\(5.\)二维费用背包

#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 10010
using namespace std;

int f[maxn][maxn];
int cnt;
int n,V,M;

int main()
{
	cin>>n>>V>>M;
	int v,w,m;
	for(int i=1;i<=n;i++)
	{
		cin>>v>>m>>w;
		for(int j=V;j>=v;j--)
		{
			for(int k=M;k>=m;k--) f[j][k]=max(f[j][k],f[j-v][k-m]+w);
		}
	}
	printf("%d\n",f[V][M]);
	return 0;
}

\(6.\)分组背包

\(f[i][j]\)表示前\(i\)组背包容量为\(j\)的最优值

但是我们可以优化空间复杂度

必须先枚举体积,再枚举决策

#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 1010
using namespace std;

int f[maxn];
int s;
int v[maxn],w[maxn];
int n,V;

int main()
{
	cin>>n>>V;
	for(int i=1;i<=n;i++)
	{
		cin>>s;
		for(int j=1;j<=s;j++) cin>>v[j]>>w[j];
		for(int j=V;j>=1;j--)
		{
			for(int k=0;k<=s;k++)//可以不选,所以从0开始 
			{
				if(j>=v[k]) f[j]=max(f[j],f[j-v[k]]+w[k]);
			}
		}
	}
	printf("%d\n",f[V]);
	return 0;
}

\(7.\)求方案数

\(f[i]\)表示背包容量为\(i\)时能装入的最大价值(同上),\(c[i]\)表示背包容量为\(i\)时的最优选法方案数

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 1010
using namespace std;

const int mod=1e9+7;

int f[maxn],c[maxn];

int main()
{
	int n,V;
	cin>>n>>V;
	for(int i=0;i<=V;i++) c[i]=1;//未装入物品时f[i]=0,c[i]=1(因为不装物品也是一种方案)
	for(int i=1;i<=n;i++)
	{
		int v,w;
		cin>>v>>w;
		for(int j=V;j>=v;j--)
		{
			if(f[j-v]+w>f[j])
			{
				f[j]=f[j-v]+w;
				c[j]=c[j-v];
			}
			else if(f[j-v]+w==f[j])
			{
				c[j]=(c[j]+c[j-v])%mod;
			}
		}
	}
	printf("%d\n",c[V]);
	return 0;
}

\(8.\)求具体方案

\(01\)背包的具体方案:这里设\(f[i][j]\)表示从第\(i\)个物品到最后容量为\(j\)的最优值,而\(01\)背包中,\(f[i][j]\)表示从第一个物品到第\(i\)个的最优值

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define maxn 1010
using namespace std;

int f[maxn][maxn];
int v[maxn],w[maxn];

int main()
{
	int n,V;
	cin>>n>>V;
	for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
	for(int i=n;i>=1;i--)
	{
		for(int j=0;j<=V;j++)
		{
			f[i][j]=f[i+1][j];
			if(j>=v[i]) f[i][j]=max(f[i][j],f[i+1][j-v[i]]+w[i]);
		}
	}
	int j=V;
	for(int i=1;i<=n;i++)
	{
		if(j>=v[i]&&f[i][j]==f[i+1][j-v[i]]+w[i])
		{
			printf("%d ",i);
			j-=v[i];
		}
	}
	return 0;
}

完全背包的具体方案

\(21\)行和\(27\)行两个\(i+1\)处改为\(i\)即可,因为我们的完全背包可以从同一行更新得到

posted on 2023-08-03 14:42  dolires  阅读(30)  评论(0)    收藏  举报

导航